面試官:如何設計一個十億級的 URL 短鏈系統?
在后端面試中,系統設計題是檢驗一位工程師架構思維和技術深度的試金石。其中,“設計一個短鏈系統”可以說是最高頻的題目之一。
這個問題看似簡單——不就是把長網址變短嗎?但如果你真的這么認為,那可能就危險了。一個看似基礎的功能背后,隱藏著海量數據存儲、高并發處理、服務高可用等一系列復雜的工程挑戰。面試官拋出這個問題,正是想看看你如何抽絲剝繭,從一個簡單的需求出發,逐步構建出一個穩健、高效、可擴展的十億級系統。
今天,秀才就帶大家來模擬一次真實的面試過程,一步步剖析如何完美地回答這個問題,讓你在面試中脫穎而出。

一、什么是短鏈系統?
首先,我們得確保和面試官在同一個“頻道”上。短鏈系統,顧名思義,就是將一個冗長的URL(例如一篇新聞的鏈接)轉換成一個非常短的URL。當用戶訪問這個短鏈接時,系統會自動將他重定向到原始的長鏈接地址。
這個過程的核心在于“生成”和“重定向”。

上圖清晰地展示了整個交互流程:
- 生成:應用程序將原始長URL發送給短鏈系統。
- 返回:短鏈系統生成一個唯一的短URL,并返回給應用程序。
- 訪問:用戶點擊短URL。
- 重定向:瀏覽器訪問短鏈系統,系統查詢到原始長URL后,返回一個HTTP 302重定向響應,瀏覽器隨即訪問原始的URL目標服務器。
二、為什么我們需要URL短鏈系統?
明確了“是什么”,接下來就要理解“為什么”。在面試中,能清晰地闡述一個技術的業務價值,是體現你商業思考和產品思維的加分項。
URL短鏈系統的價值主要體現在以下幾個方面:
- 美觀與便捷:在社交媒體(如微博)、短信、二維碼等對字符長度有限制的場景下,短鏈接是剛需。它更易于分享、打印和口頭傳播,用戶也更不容易輸錯。
- 數據追蹤與分析:這是短鏈接一個非常核心的商業價值。通過在短鏈接的重定向上增加統計邏輯,我們可以追蹤到鏈接的點擊次數、點擊來源、用戶地域分布、設備信息等,為運營決策和廣告效果衡量提供精準的數據支持。
- 功能擴展:短鏈接可以作為一層中間代理,在這層代理上我們可以實現很多高級功能,比如鏈接的有效期控制、訪問密碼保護、設備跳轉控制(移動端和PC端訪問跳轉到不同頁面)等。
- 隱藏原始鏈接:在某些推廣場景下,可以隱藏真實的、帶有復雜參數的原始URL。
比如,一個很長的商品鏈接:https://www.designsystem/shyfeawou/csline/ccosifhyew/online-course/clare-system-design-interview
可以被縮短為:https://tinyurl.com/vzxt58la
長度縮短了近三分之二,無論是在哪個場景下使用,都顯得清爽利落。
三、面試實戰指南
好了,背景知識鋪墊完畢,現在正式進入面試環節。系統設計題的回答,切忌一上來就拋出最終方案。一個優秀的回答過程,應該像一次與面試官共同探索的旅程,充滿互動和逐步深入的思考。
1. 需求分析:一切設計的起點
面試官:“我們來聊聊系統設計吧。如果要你設計一個高性能的短鏈系統,你會怎么入手?”
這是一個開放性問題,千萬不要直接回答:“我會用Redis、用哈希算法……”。正確的起手式是確認需求。這能體現你嚴謹的工程素養。
你可以這樣回應:
“在開始設計之前,我想先和您明確一下系統的具體需求和目標范圍,這樣我們的討論會更有針對性。比如,我們需要支持哪些核心功能?對系統的性能和可用性有什么樣的要求?”
通過這樣的提問,你就把問題拋給了面試官,也展示了你的專業性。并且更重要的是,可以從面試官那里確認出這個系統的一個大概的需求范圍。假設經過溝通,你們明確了以下需求:
(1) 功能性需求:
- 生成短鏈:輸入一個長URL,系統能生成一個唯一的、更短的別名(短鏈接)。
- 支持自定義:用戶可以選擇性地為他們的URL指定一個有意義的自定義短鏈接。
- 重定向:訪問短鏈接時,系統必須能準確、快速地重定向到原始的長鏈接。
- 過期機制:鏈接可以設置過期時間,過期后失效。
(2) 非功能性需求:
- 高可用:系統必須7x24小時可用。因為一旦服務宕機,所有短鏈接都將失效,這在很多場景下是災難性的。
- 高性能:重定向過程必須延遲極低,用戶幾乎無感知。
- 唯一且不可預測:生成的短鏈接必須是唯一的,并且不能被輕易地猜到規律,以防被惡意遍歷。
(3) 擴展性需求:
- 數據分析:需要支持統計短鏈接的訪問數據。
- 開放API:系統應提供REST API,方便其他服務接入。
2. 容量預估:用數據指導架構
需求明確了,下一步就要進行一個容量預估了。對于后端設計者而言,必須要做一個容量的估算,這樣才方便我們選定合適的方案。
“需求很清晰了。接下來,我想做一個簡單的容量預估,這有助于我們判斷系統的量級,從而選擇合適的技術方案。”
這是展示你架構設計能力的關鍵一步。沒有數據支撐的設計都是空中樓閣。
(1) 流量估算:假設我們是一個快速發展的業務,預計每月新增5億個短鏈接。短鏈系統的讀寫比通常非常懸殊,讀取(重定向)遠多于寫入(生成)。我們假設讀寫比例為 100:1。
- 寫入QPS:每月5億次寫入,那么每秒的寫入請求大約是 5億 / (30天 * 24小時 * 3600秒) ≈ 200次/秒。
- 讀取QPS:根據100:1的讀寫比,讀取請求大約是 200 * 100 = 2萬次/秒。這個并發量已經不低了。
(2) 存儲估算:假設每個短鏈接數據(包括長URL、短URL、創建時間、用戶ID等)我們存儲5年。
- 總記錄數:5億/月 * 12個月/年 * 5年 = 300億。這是一個非常龐大的數字。
- 單條記錄大小:我們粗略估計每條記錄為500字節。
- 總存儲空間:300億 * 500字節 ≈ 15TB。這個存儲需求對于單機數據庫來說是無法承受的。
(3) 帶寬估算:
- 入口帶寬(寫):200次/秒 * 500字節/次 ≈ 100 KB/s。
- 出口帶寬(讀):2萬次/秒 * 500字節/次 ≈ 10 MB/s。
(4) 緩存估算:為了提升讀取性能,緩存是必不可少的。根據二八原則,80%的訪問量集中在20%的熱點鏈接上。
- 每日總請求數:2萬次/秒 * 3600秒/小時 * 24小時/天 ≈ 17億次。
- 緩存目標:我們需要緩存這17億次請求中的20%。
- 所需內存:17億 * 20% * 500字節 ≈ 170GB。
將這些估算結果匯總成一個表格,展示給面試官,會顯得非常專業:
指標 | 估算值 |
新增URL(寫QPS) | ~200/s |
URL重定向(讀QPS) | ~20K/s |
入口帶寬 | 100KB/s |
出口帶寬 | 10MB/s |
5年總存儲 | 15TB |
緩存所需內存 | 170GB |
通過估算,我們可以看到,這個系統面臨的是高并發讀取和海量數據存儲兩大挑戰。這將是我們后續架構設計的核心出發點。
3. 系統接口定義(API Design)
在宏觀設計之前,先定義微觀的接口,是一種自底向上的優秀設計習慣。這有助于我們厘清系統的邊界和能力。
接下來我們可以設計一套RESTful API來暴露服務能力。主要包括以下幾個接口:
(1) 創建短鏈 API
請求路由:GET /{shortened_url}
請求參數:
參數 | 釋義 |
original_url | 需要縮短的原始長網址(字符串,必填) |
custom_alias | 用戶希望指定的短網址自定義別名(字符串,可選) |
expiration_date | 短網址應過期的日期和時間(時間戳,可選) |
user_id | 創建縮短 URL 的用戶 ID(如果支持用戶賬戶功能)(字符串,可選) |
響應參數:
參數 | 釋義 |
shortened_url | 由服務生成的縮短后 URL(字符串) |
creation_date | URL 被縮短時的日期和時間(時間戳) |
expiration_date | 短網址的過期日期(時間戳,如提供) |
(2) 重定向 API
將用戶從短鏈接重定向至原始長網址。
請求路由:GET /{shortened_url}
請求參數:
參數 | 釋義 |
shortened_url | 需要解析為原始網址的短鏈接(字符串,必填) |
響應:重定向至 original_url。
(3) 數據分析接口
提供短鏈接的詳細分析數據,包括點擊次數和用戶人口統計信息。
請求路由:GET /analytics/{shortened_url}
請求參數:
參數 | 釋義 |
shortened_url | 需要獲取分析數據的短鏈接(字符串,必填) |
start_date | 用于篩選分析數據的開始日期(時間戳,可選) |
end_date | 用于篩選分析數據的結束日期(時間戳,可選) |
響應參數:
參數 | 釋義 |
click_count | 該短鏈接被點擊的總次數(整數) |
unique_clicks | 點擊該短鏈接的唯一用戶數(整數) |
referring_sites | 為該短鏈接帶來流量的來源網站(列表) |
location_data | 點擊該網址的用戶地理分布(地圖) |
device_data | 點擊 URL 所使用的設備(移動設備、桌面設備等)的細分(映射) |
(4) 網址管理 API
請求路由: GET /user/urls
請求參數:
參數 | 釋義 |
user_id | 請求其短網址的用戶 ID(字符串, 必需) |
page | 分頁結果的頁碼(整數, 可選) |
page_size | 每頁的結果數量(整數,可選) |
響應參數:
參數 | 釋義 |
urls | 用戶縮短的 URL 列表,包括創建日期和過期日期等元數據(列表) |
(5) 刪除短鏈接 API
請求路由: DELETE /{shortened_url}
請求參數:
參數 | 釋義 |
shortened_url | 需要刪除的短鏈接(字符串,必填) |
user_id | 請求刪除的用戶 ID(字符串,必填) |
響應參數:
參數 | 釋義 |
status | 刪除確認信息或操作失敗時的錯誤提示(字符串) |
面試官:“接口設計得不錯。但有個問題,如果惡意用戶瘋狂調用創建接口,把我們生成的短碼耗盡了怎么辦?”。這是一個很好的追問,考察你對系統安全性的思考。
“如果不加限制,面對惡意調用,短url肯定很快會被耗盡。所以我們需要加入防濫用機制。核心手段是API速率限制。我們可以基于用戶ID或IP地址進行限流,比如,限制每個IP每分鐘只能創建10個短鏈接。對于未登錄的匿名用戶,這個限制可以更嚴格。”
4. 數據庫設計
基于前面的需求和接口,我們可以設計出數據庫的表結構。
“考慮到300億的記錄量和高并發讀取的需求,關系型數據庫可能不是最佳選擇。因為分庫分表會非常復雜,而且我們數據之間的關系很簡單。我更傾向于使用NoSQL數據庫,比如AWS的DynamoDB、阿里云的Table Store (OTS) 或者開源的Riak,它們的水平擴展能力更強。”
我們可以設計以下幾張表(以NoSQL的寬表模型為例):
(1) URL表 (url_mapping)
- Hash (Partition Key): 短鏈碼,例如 vzet59pa。這是我們的主鍵,用于快速查找。
- OriginalURL: 原始長鏈接。
- CreationDate: 創建時間。
- ExpirationDate: 過期時間。
- UserID: 創建者ID。
(2) 用戶表 (users)
- UserID (Partition Key): 用戶ID。
- Name, Email, PasswordHash, ...
(3) 分析表 (analytics)
- AnalyticsID (Partition Key): 唯一分析記錄ID。
- URLHash: 關聯的短鏈碼。
- ClickTimestamp: 點擊時間。
- ReferringSite: 來源網站。
- Location: 地理位置。

5. 核心算法:如何生成短鏈碼?
面試官:“數據庫選型和表結構設計都比較合理。現在我們來聊聊最核心的部分,這個短鏈碼(比如 vzet59pa)到底該怎么生成呢?”
這是整個設計的靈魂。你可以提出幾種方案,并分析優劣,展示你的技術廣度和深度。
(1) 方案一:哈希編碼
最直接的想法是對原始長URL進行哈希,然后取一部分作為短鏈碼。
“一個直接的思路是對原始URL進行哈希,比如用MD5生成一個128位的哈希值,然后通過Base64編碼將其轉換為字符串。編碼方式有很多種,比如base36 (a-z, 0-9) 或 base62 (A-Z, a-z, 0-9),如果加上 + 和 / 就是標準的Base64編碼。”
標準的Base64編碼表如下:

在使用標準Base64時,我們需要考慮一個問題:短鏈碼的長度應該設為多少?6位、8位還是10位?
- 使用Base64編碼,一個6位長度的短碼,可以產生 64^6 ≈ 687億 個可能的組合。
- 使用Base64編碼,一個8位長度的短碼,可以產生 64^8 ≈ 281萬億 個可能的組合。
“假設6位長度的短碼(687億)已經足夠滿足我們的系統需求。如果我們使用MD5算法,它會生成一個128位的哈希值,經過Base64編碼后會得到一個超過21個字符的字符串。我們只取前6位或8位作為短碼,這可能會導致沖突。為了解決這個問題,我們可以采取一些策略,比如當發現沖突時,換取哈希值的其他部分,或者對原始URL加鹽后重新哈希。”
“不過,這個方案還有幾個更麻煩的問題。第一,標準Base64中的 + 和 / 字符在URL中是特殊保留字符,其中“+”和“/”在URL中會被編碼為“%2B”以及“%2F”,?“%”在寫?數據庫的時候?和SQL編碼規則沖突,需要進?再編碼,因此直接使?標準Base64編碼進?短URL編碼并不合適,直接使用會導致問題。URL保留字符編碼表如下。

“所以,我們需要對Base64編碼進行改造,使其對URL友好。一個常見的做法是將 + 替換為 -,將 / 替換為 _。這樣改造后的字符集就完全可以在URL中安全使用了。”

“第二,如果多個用戶輸入同一個URL,他們會得到相同的短鏈接,這在某些需要獨立追蹤的場景下是不可接受的。
第三,URL的部分內容如果經過編碼,例如 .../?id=design 和 .../%3Fid%3Ddesign,本質上是同一個URL,但哈希值卻完全不同。針對這些問題,我們可以為每個輸入URL附加一個遞增的序列號或者唯一的用戶ID來確保唯一性,不過這個序列號無需存入數據庫。這種方法可能帶來的問題是序列號會無限增長——是否存在溢出風險?同時附加遞增序列號也會影響服務性能,引入新的復雜度。”

(2) 方案二:自增ID轉碼(推薦)
我們可以有一個獨立的密鑰生成服務(KGS),它預先生成隨機的六字母字符串并將其存儲在數據庫中(我們稱之為 key-DB)。每當我們想要縮短一個 URL 時,我們會取一個已經生成的密鑰并使用它。這種方法就簡單了,我們不僅不需要對 URL 進行編碼,而且也不必擔心重復或沖突。KGS 將確保所有插入到 key-DB 中的密鑰都是唯一的。生成六字母字符串的方式這里我們可以用自增ID轉碼的方式來實現
“考慮到哈希方案的種種復雜性,我更推薦另一種方案:發號器 + 進制轉換。我們可以使用一個全局唯一的ID生成服務(類似Snowflake算法或數據庫自增ID),每當有新的短鏈生成請求時,就獲取一個唯一的十進制ID。”
比如,我們獲取到了ID 10086。然后,我們將這個ID從十進制轉換為62進制(a-z, A-Z, 0-9)。
10086 (10進制) = 2Bi (62進制)
這樣,2Bi 就是我們的短鏈碼。這個方案的優點非常突出:
- 絕對唯一:每個ID都是唯一的,所以轉換后的短鏈碼也絕對不會沖突。
- 長度可控:生成的短鏈碼長度是遞增的,可以從很短開始,有效利用了碼空間。
- 性能高效:轉換過程是純計算,速度極快。
面試官:“這個方案聽起來不錯。但這個全局ID生成器不就成了系統的單點了嗎?如果它掛了,整個服務就不可用了。”
“您提到了關鍵點。這個方案通常被稱為密鑰生成服務 (Key Generation Service, KGS)。為了解決單點問題,KGS本身需要設計成高可用的集群。我們可以為KGS設置備用副本,當主服務器宕機時,備用服務器就能接管。同時,KGS可以預先生成大量的唯一短碼,并存入一個專用的數據庫(Key-DB)。為了提升性能和可用性,我們可以設計兩張表:一張存未使用的密鑰,一張存已使用的。當KGS分配密鑰給應用服務器時,就將其從未用表移動到已用表。KGS還可以在內存中緩存一批密鑰,以便快速響應。為了避免多個應用服務器同時請求到同一個密鑰,KGS在分配密鑰時需要加鎖同步。”
“關于這個密鑰數據庫的大小,我們也可以估算一下。采用62進制,6位長度的短碼,大約有 62^6 ≈ 568億 種組合。如果每個字符占1字節,那么存儲所有密鑰需要 6 * 568億 ≈ 340GB 的空間。”

我們如何進行密鑰查找?我們可以在數據庫中查找該密鑰以獲取完整的 URL。如果該密鑰存在于數據庫中,則向瀏覽器返回一個“HTTP 302 重定向”狀態,并在請求的“Location”字段中傳遞存儲的 URL。如果該密鑰不在我們的系統中,則返回一個“HTTP 404 未找到”狀態或將用戶重定向回主頁。
6. 數據分區與復制
前面我們估算出有15TB的數據,單機數據庫肯定扛不住,所以必須進行數據分區(Sharding)。
面試官:“具體說說你會怎么分區?”
主要有兩種思路:
- 基于范圍的分區:我們可以根據短鏈碼的首字母來分區。比如所有以'a'開頭的存一個分片,'b'開頭的存另一個。以此類推。這種策略稱為基于范圍的分區。我們甚至可以將某些出現頻率較低的字母組合存入同一個數據庫分區。
這種方式實現簡單,但很容易導致數據傾斜和熱點問題。例如,我們決定將所有以字母'E'開頭的 URL 存入某個數據庫分區,但后來發現以'E'開頭的 URL 數量過多。
- 基于哈希的分區:這是更優的選擇。我們對短鏈碼(Hash)進行哈希計算,然后對分片總數取模,決定這條記錄應該存儲在哪個數據庫分片上。例如 hash(short_code) % 256。這種方式能讓數據均勻分布。
為了更好地處理增刪節點帶來的數據遷移問題,我們還可以引入一致性哈希算法。同時,為了保證數據的高可用,每個數據庫分片都應該有主從副本,實現讀寫分離和故障轉移。
7. 緩存策略
為了應對每秒2萬次的讀取請求,緩存是必不可少的一環。我們可以在應用服務器和數據庫之間加入分布式緩存層,比如Redis或Memcached。
我們可以對高頻訪問的 URL 進行緩存。采用 redis 等現成解決方案即可存儲完整 URL 及其對應哈希值。這樣應用服務器在訪問后端存儲前,能快速檢查緩存中是否存在目標 URL。
緩存內容:緩存 短鏈碼 -> 原始長URL 的映射關系。
工作流程:
- 當請求到達時,應用服務器首先查詢緩存。
- 如果緩存命中(Cache Hit),直接從緩存中獲取原始URL并返回重定向。
- 如果緩存未命中(Cache Miss),則查詢后端的數據庫。
- 從數據庫查到數據后,先將其寫入緩存,然后再返回給用戶。這樣后續相同的請求就能直接命中緩存了。
面試官:那需要配置多大的緩存內存呢?
我們可以從每日流量的 20%開始,根據客戶端使用模式動態調整所需緩存服務器的數量。根據前文估算,緩存 20%的日流量需要 170GB 內存。由于現代服務器可配備 256GB 內存,我們完全可以用單臺機器承載全部緩存。當然,也可以選擇用多臺配置較低的服務器來存儲這些熱門 URL。
哪種緩存淘汰策略最適合我們的需求?
可以使用 LRU (Least Recently Used) 策略。因為熱點鏈接會經常被訪問,而冷門鏈接則會逐漸被淘汰出緩存,這符合我們的業務場景。
同時,為了進一步提升效率,我們可以部署緩存集群。當發生緩存未命中并從數據庫取回數據后,應用服務器需要將這個新條目寫入所有緩存副本,以保證數據一致性。

8. 負載均衡
十億級別的短鏈存儲,在業務上訪問量應該不低,所以服務實例基本上都要采用多節點部署。因此我們可以在系統中的三個位置添加負載均衡層。
“我們的應用服務、緩存服務、數據庫服務都會是集群部署,所以在系統的關鍵節點都需要部署負載均衡器(Load Balancer)。”
- 客戶端 -> 應用服務器
- 應用服務器 -> 數據庫集群
- 應用服務器 -> 緩存集群
“最初,我們可以采用簡單的輪詢策略。但這種策略不關心服務器的實際負載。如果某臺服務器因為某些原因響應變慢,輪詢策略依然會給它分配新請求,可能導致問題惡化。因此,更智能的方案是采用最少連接或基于響應時間的加權輪詢等策略,負載均衡器會定期探測后端服務器的健康狀況和負載,動態地調整流量分配。”
9.過期鏈接清理
面試官:“設計得差不多了。最后一個問題,那些設置了過期時間的鏈接,你們是怎么處理的?會有一個定時任務去掃描數據庫刪除嗎?”
這是一個考察系統維護和資源優化的好問題。
主動掃描整個龐大的數據庫去刪除過期鏈接,成本太高,會對數據庫造成不必要的壓力。我們可以采用更優雅的惰性刪除(Lazy Deletion)和異步清理結合的策略。
- 設置默認過期時間:我們可以為每個鏈接設置默認有效期(例如兩年)
- 訪問時刪除:當一個用戶訪問某個短鏈接時,我們在查詢到數據后,會檢查其是否過期。如果已過期,我們不會返回重定向,而是直接刪除該記錄,并向用戶返回一個“鏈接已失效”的頁面。
- 異步清理:同時,我們還會有一個低優先級的后臺清理服務,它會在系統負載較低的時候(比如凌晨),慢慢地、分批地去清理那些已經過期但長時間未被訪問的“僵尸”鏈接。這個服務必須被設計成輕量級的,避免影響核心業務。
- 密鑰復用:在刪除了過期鏈接后,我們可以將它對應的短鏈碼回收,放回到KGS的未使用密鑰庫中,實現循環利用。

四、小結
至此,一個相對完整、考慮周全的十億級短鏈系統設計方案就展現在面試官面前了。我們再來回顧一下整體架構。
從一個簡單的需求出發,我們通過需求分析、容量預估,明確了系統的核心挑戰。然后,我們定義了清晰的API接口,設計了可擴展的數據庫模型,并深入探討了最核心的短碼生成算法。為了應對高性能和高可用的要求,我們引入了數據分區、緩存和負載均衡等關鍵組件。最后,我們還考慮到了過期數據清理這樣的維護性細節。
整個回答過程,不僅展示了你的技術深度和廣度,更重要的是,體現了你作為一名架構師,那種從全局出發、層層遞-進、權衡利弊的系統化思維方式。這才是面試官最想看到的。



































