免費完整指南

短網址服務

為 1 億日活躍使用者設計一套可擴展的短網址服務。涵蓋轉址、自訂別名、點擊分析、快取,以及一致性的取捨。

00

練習檢查點

面試節奏保持精簡,讓頁面能把注意力花在真正的設計決策上。

  1. 01
    釐清範圍
  2. 02
    需求 + 規模
  3. 03
    API + 資料模型
  4. 04
    畫出架構
  5. 05
    深入探討
  6. 06
    取捨決策
01

形塑設計的需求

不要只是陳述需求,要主動問出來。每張卡把設計限制和一句你可以在畫架構前說出口的釐清問句配成一組。

功能需求

01使用者應該能挑自訂別名,還是系統產生的短碼就夠了?

使用者可以提交長網址,換回一個唯一的短連結——預設是系統產生的 base62 短碼(字母 + 數字),自訂別名是選用的額外功能。

02連結永久存在,還是我們需要過期與清理?

使用者可以設定選用的過期時間;過期的連結停止轉址,其短碼稍後可以回收。

03轉址本身就是核心流程嗎——點擊當下還必須發生別的事嗎?

任何人打開短連結都會被轉址到原始網址——這是最頻繁的操作,遙遙領先。每一次點擊也都必須被記錄,而記錄絕不能拖慢轉址。

04行銷活動需要大量建立嗎——單次呼叫產生數千條連結?

支援批次建立:一檔行銷活動在單一請求中產生數千條連結。

05連結建立後可以更改目標到新的網址嗎?

連結一旦建立就不會變。重新指向一個新目的地是可選的加值功能。重新指向之後,訪客必須抵達新目的地,絕不是舊的那個。

06連結的擁有權歸誰——建立者能列出並停用自己的連結嗎?

連結屬於它的建立者:可列出、停用、退役——被停用的連結必須在數秒內停止轉址。

範圍外分析儀表板——只有非同步的點擊事件送出在範圍內 · 使用者帳號、驗證,以及連結管理的 UI · 垃圾與惡意網址掃描

非功能需求

01我應該假設什麼讀寫比——這是嚴重偏讀取的嗎?

讀取為主:一般一天大約 100M 人點開短連結,相對於大約 1M 個新建連結。那至少是 100:1 的轉址對建立比。行銷導向的部署更接近 1000:1,所以值得問清楚這是哪一種。

02轉址對使用者要感覺多快?

轉址 p95 低於 100 ms——短連結這一跳對點擊的人必須感覺是隱形的。

03全新的連結必須立刻在各處都能解析,還是短暫延遲可以接受?

可用性優先於一致性:轉址目標 99.99%。一個剛建立的連結可能要花幾秒才傳到每一個快取與複本(接受最終一致性)。

04短碼空間與儲存量應該規劃到多少條連結?

按 ~1B 個連結來規劃。一個 7 字元的 base62 code 給出 62^7 ≈ 3.5 兆個可能的 code,餘裕充足。每列約 1 KB,總儲存維持在 1 TB 附近,可依 short code 分片。

05兩條連結有可能共用一個短碼嗎——有人能猜到私密短碼嗎?

短碼必須全域唯一——永不碰撞。私密連結的短碼不能被猜到:可預測的連號會讓陌生人一條一條枚舉出私密網址。

持續追問 —— 面試是一場對話

真實面試探得比一份整齊清單深得多。這些範圍問句,區分出真正拷問問題的人和只是背誦的人。

  • 點擊歷史必須保留多久——死掉的連結需要為了稽核而保持可查詢嗎?
  • 任何人不用帳號就能建立連結嗎,還是我們需要每使用者的限額來壓住濫用?
  • 客戶想要掛在自家品牌網域下的短連結,還是只用我們的?
  • 使用者住在哪裡——轉址該在全球一樣快,還是流量集中在單一區域?
  • 點擊記錄算個人資料嗎——對於能存什麼、存多久,有沒有隱私規則?
02

逼出架構決策的數字

把每個估算都當成一種壓力,用來合理化一個元件:快取、佇列、分片、副本、worker pool 或退路。

01

轉址讀取 QPS

1 億每日使用者(前面讀取密集的規模設定)× 每人每日約 1 次轉址 = 每日 1 億次轉址100,000,000 次轉址 ÷ 一天 86,400 s ≈ 平均 1,160 QPS · 爆紅突發 ×5-10 ≈ 尖峰 5-10K QPS

快取 + CDN 必須吸收尖峰——資料庫永遠不服務轉址熱路徑。

02

建立寫入 QPS

每日約 100 萬條新連結1,000,000 ÷ 86,400 秒 ≈ 每秒 12 次寫入

寫入微不足道,所以不需要為寫入做擴展。寫入端唯一的風險是搶 code 分配,而不是列的插入。

03

總儲存量

10 億條連結 × 每列約 1 KB(短碼、長網址、擁有者、時間戳、TTL)10⁹ × 1 KB ≈ 1 TB

輕鬆放進分片儲存;short_code 索引才是真正的工作集。

04

短碼空間

7 字元的 base62 短碼(每個位置 62 種字元)62⁷ ≈ 3.5 × 10¹² 個短碼 vs 需要的 10⁹ 條連結 → 約 3,500 倍餘裕

短碼永遠用不完——真正的風險是可預測性(可枚舉的短碼),而不是耗盡。

05

快取大小

假設約 20% 的連結服務約 80% 的讀取(Pareto)20% × 1 TB ≈ 200 GB

在一個中等規模的 Redis 叢集上就可行。最熱門的爆紅連結也坐在 CDN 邊緣上。

決策範例

數字

數字全指向同一個方向。讀取比寫入多大約 100 比 1,尖峰每秒 5-10K 次轉址,每次要在 100 ms 內回應。所以轉址是唯一值得最佳化的路徑。

我的選擇

轉址先由快取回應——Redis,加上給最熱門連結用的 CDN。這樣資料庫就永遠不在熱路徑上。至於建立連結,一個小小的 key 服務發給每台 API 伺服器一批預先做好的唯一 code,所以建立永遠不用去搶一個共享的計數器。點擊的計數靠把一個事件丟上串流來完成。轉址永遠不等分析。

避免

我不會做的事:對長網址雜湊來產生短碼、然後在兩個網址碰撞時重試。在高負載下這些重試會堆積,建立連結就變成抽獎。發放預先做好的唯一短碼,讓碰撞變成不可能,而不只是不太可能。

何時改變

如果精確的每次點擊分析變成必備,我會從 301 轉址改成 302。這樣瀏覽器就不再快取那一跳,於是每次點擊都會抵達我的伺服器並被算到。我接受那一點點多出來的延遲。

03

架構路徑

先看一張完整的圖,再把每條路徑各自畫成一張圖 —— 寫入路徑與讀取路徑承載不同流量,合理化不同的元件。

完整全貌

總覽 —— 每個元件

ClientCDN / EdgeAPI ServiceCacheDatabaseKey GenerationService取得短碼範圍Analytics Stream非同步點擊事件

先從簡單版開始:一張圖,畫出每個元件與關係。虛線 = 非同步、不在熱路徑上。快取 miss 時退回資料庫並回填快取。

路徑 1

寫入路徑——建立一條短連結

ClientAPI ServiceKey GenerationServiceDatabaseCache(預先回填)

KGS 發放預先產生的唯一短碼範圍,所以建立永不碰撞、也永不為共用 counter 阻塞。

路徑 2

讀取路徑——轉址(熱路徑)

ClientCDN / EdgeAPI ServiceCache301/302 轉址

快取 miss 時退回資料庫並回填快取;點擊事件走非路徑的分析串流。

04

API 與資料模型

在最佳化之前,先讓契約可被檢視:端點、實體、擁有權、重試與狀態。

POST/urls

請求{ long_url, custom_alias?, expires_at? }

回應200 { short_url, expires_at } · 409 別名已被占用 · 400 網址無效

對 (owner, long_url) 冪等:重複提交回傳既有的 short_url,而不是產生新短碼。

GET/{short_code}

回應301/302 + Location: long_url · 404 未知短碼 · 410 已過期

301 讓瀏覽器快取這次轉址(回源更少,但失去點擊分析);302 讓每次點擊都經過你的伺服器(保住分析,多一跳)。這個選擇取決於分析需求——把它講出來。

核心實體

ShortLink

short_code (PK) · long_url · owner_id · created_at · expires_at · is_active

依 short_code 分片,讓一次轉址只打到一個分區;選用的反向索引 long_url → short_code 可支援建立時去重。

User

user_id (PK) · email · created_at

05

深入方向

在面試最後三分之一挑一條路線。每條路線給你主題、它該回答的面試官問題,以及要避免的失敗模式。

重點

短碼如何產生

你如何保證兩條連結永遠不會拿到同一個短碼——counter + base62,還是雜湊?為什麼選那個?

回答

短碼預先產生,別用雜湊。一個獨立的服務事先把唯一的短碼做好,再把一整批分給每台 API server 自己去發。兩台 server 不可能挑到同一個碼,也沒人需要先查資料庫——就算行銷活動再大也一樣。雜湊只是讓碰撞變少而已,而每次碰撞都要重試一次,偏偏發生在負載最高的時候。

避免

對網址雜湊、寄望碰撞很罕見,卻不解釋唯一性保證。

重點

讓轉址變快

走一次端到端的轉址:快取在哪裡命中、miss 時發生什麼、資料庫到底何時才被碰到?

回答

先從 Redis 讀短碼。命中的話幾毫秒就回應,完全不碰資料庫;沒命中就讀一次,再回填快取。最熱門的連結還會放在 CDN edge 上。連結被停用或改目標時,別等 TTL——直接從 Redis 刪掉,並馬上清掉 CDN 上的副本,改動就能在幾秒內生效。

避免

每次轉址都讀資料庫——熱路徑必須快取優先。

重點

301 還是 302

你回傳哪個轉址狀態碼?那個選擇對瀏覽器快取與你的點擊資料有什麼影響?

回答

這裡用 302,因為我們得數到每一次點擊。302 會讓瀏覽器每次都問一次 server,所以每次點擊都看得到——代價是多一跳。301 則讓瀏覽器永久快取這個轉址:比較快,但重複點擊就變成隱形的。既然記錄點擊是需求,302 勝出。

避免

隨意挑一個——這個選擇就是分析決策本身。

重點

在不拖慢轉址的前提下計數點擊

你如何在不增加延遲的前提下為 1 億 DAU 記錄點擊?如果分析管線落後了會怎樣?

回答

轉址時把點擊事件丟進像 Kafka 這種持久化的串流,然後馬上回應——計數是之後在旁邊做的。就算那條管線落後了,數字會過時一陣子,但轉址永遠不會變慢。過時的數字是可以接受的取捨;變慢的轉址不行。

避免

在轉址內部同步寫入分析。

重點

一條連結爆紅

單一連結突然吃掉全部流量的 10%——什麼會先壞掉?快取與 CDN 如何吸收它?

回答

一條爆紅連結會把存著它的那個 cache shard 打爆,遠在資料庫察覺之前。把它放到 CDN edge 上用短 TTL 服務,幾百萬次點擊在打到我們之前就被吸收掉了。API server 也可以把它留在本機記憶體當備援。不管單一連結衝多兇,origin 的流量都維持平穩。

避免

假設流量平均分布——熱 key 才是真正的負載。

準備好練習了嗎?

把 短網址服務 大聲講一遍,讓 AI 為你的說明評分。

用 AI 練習這題 →