題目與適用情境
設計一個服務:接收 HTTP 或 HTTPS URL,回傳短網址;使用者造訪短網址時,服務重新導向至已儲存的 目標網址。基礎方案支援可選的自訂別名和到期時間。點擊分析、自訂網域、帳號管理和連結預覽作為 追問,不列入核心需求。
採用以下案例假設,確保每項容量結論都能重新計算:
- 每天建立 100 萬條短網址,發生 1 億次重新導向;
- 尖峰流量是每日平均值的 10 倍;
- 除非到期或停用,連結保留 5 年;
- 重新導向路徑目標為每月可用率 99.99%,服務端 p99 延遲低於 100 ms;
- 索引和複本之前,每條對應資料平均占 500 位元組。
這些數字只是面試案例假設,不是某個產品的實測資料。它們說明系統讀多寫少,但設計仍要保證並行 建立時短碼唯一;建立請求發生不確定逾時後能可靠回傳同一結果;到期或惡意連結能在明確的傳播 時間窗內停止服務。
面試官考察重點
第一,看需求是否收斂。好的回答會把建立和重新導向與分析、帳號功能分開,明確目標網址是否可修改, 並詢問到期和自訂別名的語意。需求未定義就先加入佇列、搜尋引擎或圖形資料庫,會削弱設計依據。
第二,看容量估算是否真正影響選擇。假設流量的每日平均約為每秒建立 12 條、重新導向 1200 次, 尖峰約為每秒建立 120 條、重新導向 12000 次。5 年約保留 18 億條對應資料,原始資料約 0.9 TB; 加上複本、索引、儲存開銷和餘量,實際配置會是數 TB。這些數量支持使用可分割的持久化儲存和快取, 但不能為所有分散式元件提供理由。
第三,看識別碼的正確性。8 位 Base62 短碼有 62^8、約 218 兆種取值。保留 18 億條時, 空間占用率低於 0.001%。單次隨機抽樣發生碰撞的機率很小,但抽樣次數足夠多後,系統曾經發生過 某次碰撞的機率會很大。隨機性降低了可預測性,並不保證唯一;持久化寫入必須原子判斷短碼不存在, 碰撞後重新產生。
第四,看讀取路徑和故障推導。快取只是最佳化,不是真實來源。到期必須在讀取時檢查,不能依賴清理 工作。熱門短碼、快取故障、資料庫逾時、重複 POST、跨區域複寫延遲、分析積壓和緊急停用都需要 明確行為。
最後,安全是重新導向服務的核心需求。短網址可能隱藏釣魚目標,可預測短碼也可能被列舉。URL 解析、 通訊協定限制、速率限制、信譽檢查、濫用檢舉、快速停用和非連續公開短碼,都應進入主要設計。
回答前需要釐清的問題
- 建立後允許修改目標網址嗎? 基礎對應不可變,這會簡化快取與稽核。若要編輯,增加版本控制和
嚴格的快取失效 SLO。
- 相同長網址是否共用一個短碼? 不共用。不同擁有者、活動、到期時間和政策可能需要不同連結;
去重應是明確選項,不能成為對目標 URL 做雜湊的意外結果。
- 必須支援自訂別名嗎? 可選,而且在選定網域內唯一。衝突回傳
409,服務不能悄悄修改別名。 - 到期時回傳什麼? 已知到期或停用回傳
410,從未存在回傳404。讀取時檢查
expires_at,非同步刪除只負責回收儲存空間。
- 採用哪種重新導向狀態碼? 預設
302,因為對應可能被停用,政策和分析也可能需要接收每次
請求。只有不可變連結且擁有者接受瀏覽器與中介快取長期保存時,才提供 301。
- 需要何種一致性? 短碼保留和自訂別名建立需要強唯一性。既有連結的重新導向優先可用性,但
成功建立後必須透過填入快取或 read-after-write 路徑立即可讀。
- 分析事件必須零遺失嗎? 基礎方案不包含分析。加入後要另外定義遺失率和資料新鮮度,分析延遲
不能阻塞重新導向。
- 服務會抓取目標內容嗎? 重新導向路徑不會。預覽或惡意內容掃描若要抓取 URL,應在具備 SSRF
防護的隔離非同步服務中執行。
30 秒回答框架
「我會把建立和重新導向作為兩條核心路徑。每天建立 100 萬條、重新導向 1 億次,對應每日平均每秒 約 12 次寫入、1200 次讀取,10 倍尖峰約為 120 和 12000。短碼使用密碼學安全亂數產生 8 位 Base62,並透過原子 insert-if-absent 保留;自訂別名採用相同條件。持久化對應按短碼雜湊分割並 作為真實來源。重新導向服務使用 cache-aside,檢查狀態和到期時間後回傳 302 Location。建立 API 具備冪等性,快取項目不晚於連結本身到期,修改或停用會主動推送失效。我會獨立擴充熱門讀取 路徑,防止快取未命中形成擊穿,把分析放在非同步路徑。最後用別名競爭、回應遺失重試、熱門鍵、 快取與資料庫故障、到期邊界和停用傳播來驗證明確的 SLO。」
分步深入解答
先列出一份簡短的容量帳本:
建立:1,000,000 / 86,400 ≈ 12/s 每日平均,10 倍尖峰約 120/s
重新導向:100,000,000 / 86,400 ≈ 1,200/s 每日平均,10 倍尖峰約 12,000/s
對應資料:1,000,000 × 365 × 5 = 18.25 億條
原始資料:18.25 億 × 500 位元組 ≈ 0.9 TB,不含額外開銷與複本
短碼空間:62^8 = 218,340,105,584,896,占用率仍低於 0.001%核心 API 可以維持很精簡:
POST /v1/links
Idempotency-Key: client-generated-key
{ "url": "https://example.com/a", "customAlias": null, "expiresAt": null }
-> 201 { "code": "aZ3kP9qR", "shortUrl": "https://sho.rt/aZ3kP9qR" }
GET /{code}
-> 302 Location: https://example.com/a
-> 404:短碼從未存在
-> 410:短碼已到期或停用核心存取模式是按短碼點查,因此一筆持久化記錄只保留建立、重新導向和生命週期政策所需欄位:
links
code 主鍵
long_url
owner_id
status ACTIVE | DISABLED
created_at
expires_at 可為空值
version
create_requests
owner_id + idempotency_key 唯一鍵
request_fingerprint
code
status
expires_at使用密碼學安全亂數產生 8 個 Base62 字元。7 位空間已經約有 3.5 兆個值,第 8 位用很小的 URL 長度代價換來更大餘量,也提高線上列舉成本。隨機方案不需要中央數字分配器,也不會公開連續序列, 但仍必須原子條件寫入:只有 code 不存在時才插入對應資料。產生的短碼衝突時,在有限次數內重新 抽樣;自訂別名衝突則回傳 409,因為改名會違反呼叫端合約。
把遞增計數器轉成 Base62 也是可行方案。只要分配器正確,它能得到唯一且精簡的值,並能透過號段 租用減少協調;代價是分配器復原、號段浪費、區域歸屬和可預測列舉。對長網址做雜湊也沒有免費解法: 截斷會碰撞,相同目標可能需要不同連結,處理碰撞最後仍要儲存。應先說明所需性質,再在隨機碼、 租用計數器和雜湊之間選擇。
建立路徑按以下順序執行:
- 視需要驗證身分並限制速率,解析 URL,只允許
http和https,限制長度和政策,正規化自訂別名。 - 檢查冪等鍵。相同鍵搭配不同請求指紋屬於衝突;相同請求應回傳原結果。
- 產生或接收短碼。在同一交易中有條件地保留短碼並儲存冪等記錄,避免兩個建立者同時取得同一別名,
也避免回應遺失後的重試建立另一條連結。
- 持久化提交後再填入或清除快取,並投遞非同步信譽掃描。不能回傳只存在於快取中的短碼。
若寫入請求逾時,客戶端使用同一冪等鍵重試。服務先讀取請求記錄,已提交就回傳原結果。盲目產生 新短碼會把一次不確定回應變成重複持久化狀態。如果儲存暫時不能證明交易是否提交,回傳處理中或 可重試結果,不能先宣告失敗再建立新對應。
重新導向時,邊緣或無狀態重新導向服務先檢查快速傳播的停用清單,再從分散式快取查詢 code。 命中後仍要檢查 status 和 expires_at;未命中則從持久化儲存點查,執行相同生命週期檢查後 寫入快取。快取 TTL 不能晚於 expires_at,一般 TTL 加少量隨機抖動,避免大量項目同時到期。 不存在的短碼可以短暫負快取以吸收掃描,但建立同名自訂別名時必須清除對應負快取。
預設回傳帶 Location 的 302。HTTP 語意把 302 定義為暫時位置,客戶端之後仍使用短網址。 301 表示永久新 URI,並可被啟發式快取;它能減少服務流量,也會拖慢撤銷、目標修改和逐請求分析。 重新導向狀態碼與 Cache-Control 是產品合約,不能只是服務內部隱藏的效能開關。
持久化層可以是鍵值資料庫,也可以是按 code 雜湊分割的關聯式資料庫。核心條件是原子 create-if-absent、持久複本、點查、備份和經過演練的復原。隨機短碼能自然分散一般流量,但一條 爆紅連結仍會成為熱門鍵。應複寫快取,為極端熱門項目增加小型本機快取,並合併並行未命中,避免 一次到期把數千個相同請求同時送到資料庫。
故障策略必須守住真實來源邊界:
- 快取不可用時,使用斷路器、有限的資料庫直接讀取、本機熱門項目和准入控制。無限制略過快取會把
快取事故升級成資料庫事故。
- 資料庫讀取不可用時,只有產品接受風險才提供有界的舊正快取;不能延長已到期連結,也不能略過
停用清單。
- 持久化寫入無法保證唯一時,建立失敗。可用性不能成為同一短碼對應兩個目標的理由。
- 分析延遲時,重新導向繼續;點擊事件按獨立的分析合約緩衝、取樣或捨棄。
- 清理工作停止時,讀取仍執行到期判斷。儲存會成長,但不會繼續服務到期連結。
安全驗證從建立開始,並持續到建立之後。使用真正的 URL 解析器拒絕非 HTTP 通訊協定和格式錯誤的 URL;按帳號、網路和風險訊號限制速率;非同步掃描目標;提供檢舉、申訴和快速停用。短碼不能承擔 私人內容的授權職責。若目標內容需要驗證權限,應由目標系統執行;短碼難猜不等於存取控制。
驗證要涵蓋性質和故障。讓大量建立者爭搶同一自訂別名,確認只有一個成功;丟掉第一次 POST 回應, 確認冪等重試取得同一短碼;測試到期前一秒、到期時刻和到期後一秒;在負快取查詢後立即建立同名 短碼;人工縮小短碼空間以觸發條件衝突。分別以寬工作集和單一熱門短碼壓到 10 倍尖峰,再關閉快取 節點、限制資料庫、延遲失效、停止清理和分析 worker。驗收應觀察重新導向成功率、p99、快取命中率、 資料庫未命中負載、條件衝突、舊連結和停用傳播,而不只報告平均吞吐量。
高品質示範回答
「我會把基礎系統限定為建立和解析短網址,支援可選的自訂別名和到期時間。我先確認目標不可修改、 相同長網址可以得到不同短碼、已知到期回傳 410,而且分析不會阻塞重新導向。
按照案例假設,建立每日平均約每秒 12 次,尖峰約 120;重新導向每日平均約每秒 1200 次,尖峰約 12000。5 年約保留 18 億條對應資料,按每條 500 位元組計算,原始資料約 0.9 TB。因此我選擇 支援點查、分割、複本和原子條件插入的持久化儲存,以 code 作為分割鍵。
產生連結時,我用密碼學亂數產生 8 位 Base62。空間約 218 兆,因此本規模下單次碰撞機率很低, 但隨機不等於唯一。我會用 insert-if-absent 保留短碼,隨機衝突就重試;自訂別名衝突回傳 409。 POST 同時攜帶冪等鍵,對應資料和請求記錄一起提交,回應遺失後仍能回傳同一短碼。
重新導向服務先檢查停用清單,再查詢快取。未命中時執行點查,檢查啟用狀態和到期時間,以不超過 剩餘有效期的 TTL 快取,並回傳帶 Location 的 302。預設採用 302,是因為對應可能被停用, 服務也可能需要逐請求政策或分析;不可變連結可以選擇 301 和更強的快取。負快取只保留很短時間, 建立自訂別名時主動清除。
持久化對應是真實來源。快取故障時只能有限地直接讀取資料庫並執行准入控制;資料庫故障時是否提供 有界的舊正快取由產品政策決定;無法保證唯一時直接讓建立失敗。熱門項目使用分層快取和未命中合併。 分析與信譽掃描非同步執行,確認惡意後透過快速控制路徑停用。
我會用並行別名競爭、回應遺失重試、到期邊界、負快取失效、熱門和寬工作集壓力、快取故障、資料庫 限流、清理故障及停用傳播來證明設計。驗收條件是在既定尖峰下重新導向可用率達到 99.99%、p99 低於 100 ms、沒有重複短碼勝出者、不服務到期連結,並量出明確的停用傳播時間窗。」
常見錯誤
- 對長網址做雜湊就認為唯一 → 截斷會碰撞,相同 URL 也可能需要不同政策 → **原子保留短碼,
並先定義是否去重。**
- 使用隨機短碼卻沒有條件寫入 → 把機率當成保證 → 只在短碼不存在時插入,並重試產生衝突。
- 寫入逾時後回傳新短碼 → 一次客戶端操作產生多條連結 → 把重試綁定至持久化冪等鍵並復原原結果。
- 先寫快取再寫持久化 → 看似成功的連結會隨快取淘汰而消失 → 先提交真實來源,再填入快取。
- 依賴刪除工作處理到期 → 工作延遲就繼續服務到期連結 → **所有解析路徑都檢查
expires_at,
清理只負責回收空間。**
- 把 301 說成「更快」、302 說成「不快取」 → 忽略可變性和明確的快取控制 → **依產品合約選擇
重新導向語意和 Cache-Control。**
- 永久快取 404 → 新建自訂別名仍不可達 → 負快取使用短 TTL,建立時主動清除。
- 所有快取未命中都直達資料庫 → 熱門項目到期形成擊穿 → 使用請求合併、TTL 抖動和分層熱門快取。
- 同步寫入分析 → 非核心管線故障拖垮重新導向 → 解析後再發事件,另行定義分析遺失與時效。
- 把短碼當成授權 → 列舉或分享會暴露私人內容 → 目標系統獨立驗證權限,短碼只作為定位符。
追問與應對
追問 1:如何增加近即時點擊分析?
重新導向決策後傳送點擊事件,包含 code、事件時間、請求 ID 和隱私政策允許的維度。按短碼分割 可保持單一連結的聚合順序,極端熱門項目則要拆分。消費者以冪等方式更新每分鐘和每日聚合。選擇確認 機制前先定義允許的遺失、重複、新鮮度、保留、機器人過濾和使用者同意;重新導向不能等待分析儲存。
追問 2:如何跨區域 active-active 部署?
透過區域快取和複本讓讀取就近。建立仍需全域唯一:使用全域條件寫入儲存、為區域分配不重疊的 隨機或數字命名空間,或把建立路由至主要區域。複寫完成前,成功建立需要 read-after-write 路徑。 停用中繼資料應使用比一般對應資料複寫更快、另外量測的傳播路徑。
追問 3:若目標網址允許編輯,需要改變什麼?
增加帶版本條件的更新、稽核記錄、擁有者授權和按短碼及版本清除快取。要明確已被客戶端快取的 301 是否允許過期;若快速修改或撤銷很重要,預設使用 302 和有界的快取新鮮度。並行編輯要攜帶預期版本, 避免一位編輯者悄悄覆寫另一位。
追問 4:單一連結每秒有數百萬次請求怎麼辦?
透過 CDN 或邊緣快取、區域快取和小型程序內快取提供服務,同時保持一致的停用檢查。應複寫熱門值, 不能試圖按短碼拆分同一個鍵。合併更新、提前更新,並隔離熱門流量,避免它耗盡全部快取或資料庫連線。 壓力測試必須涵蓋快取失效,因為最熱門的連結也最難快速撤銷。
追問 5:自訂網域如何改變鍵和路由?
唯一範圍變為 (domain, code)。需要驗證網域所有權、簽發憑證、按 Host 路由,並保留租戶配額和 濫用政策。快取鍵和資料庫分割鍵都要包含網域。同一別名存在於兩個網域時,不能互相覆寫或清除。
追問 6:法律刪除要求立即抹除目標怎麼辦?
把停止服務和實體刪除拆開。先標記停用、更新停用清單、清除快取,並驗證每個區域在停用 SLO 內 都回傳 410;之後依保留政策刪除或加密抹除持久記錄、備份、分析維度、搜尋和掃描複本。只依靠 非同步刪除工作無法證明連結已停止解析。
追問 7:如何從 8 位短碼遷移到更長短碼?
先讓解析器接受帶版本的多種長度,再改變寫入端。新寫入可以使用更長短碼,舊對應繼續原樣解析。 分割邏輯不能依賴新格式取消的固定字元位置。監控解析錯誤和快取鍵解析後再淘汰舊寫入版本;不能為了 統一長度而重寫已公開短碼。