前面的系統設計都偏 ML / 大型分散式,這篇回到入門必練的兩道經典——它們小而完整,是面試暖身與打基礎的最佳教材,也常被當作「30 分鐘快問」的題目:
- 限流器(Rate Limiter):保護後端不被打爆,考點在四種演算法的取捨與分散式實作。
- 短網址(TinyURL):把長網址換成短碼,考點在發號策略、讀多寫少的擴展與重導向選擇。
兩題都用一貫的分層拆解 + 面試官追問。
一、設計限流器 Rate Limiter
Step 1 — 釐清需求
- 限什麼:每個 user?每個 IP?每個 API key?
- 限多少:例如「每分鐘 100 次」。
- 超過怎麼辦:回 HTTP 429 (Too Many Requests),可附
Retry-After。 - 規模:單機還是分散式(多台伺服器共用同一份計數)?
- 精準度 vs 效能:要嚴格精準,還是允許邊界少量誤差換取效能?
🎤 面試官追問:「限流放在哪一層?」(通常在 API Gateway / middleware,在請求進到業務邏輯前就擋掉,省後端資源。)
Step 2 — 四種演算法與取捨 ⭐
| 演算法 | 原理 | 優點 | 缺點 |
|---|---|---|---|
| 固定視窗計數 Fixed Window | 每個時間窗(如每分鐘)一個計數器,超過就擋 | 最簡單、省記憶體 | 視窗邊界可能瞬間放行 2 倍流量 |
| 滑動視窗日誌 Sliding Log | 記錄每次請求的時間戳,數窗內筆數 | 最精準 | 記憶體重(要存每筆時間) |
| 滑動視窗計數 Sliding Window Counter | 用前一窗 + 當前窗的加權估算 | 精準與效能平衡最佳 | 近似值,非 100% 精準 |
| 令牌桶 Token Bucket | 桶以固定速率補令牌,每請求耗一個;桶空則擋 | 允許突發(攢著的令牌)、平滑 | 參數(桶容量/補速)要調 |
| 漏桶 Leaky Bucket | 請求進佇列、以固定速率流出 | 輸出絕對平滑 | 不允許突發、可能排隊延遲 |
怎麼選:
- 要允許短時突發(多數 API)→ Token Bucket(業界最常用)。
- 要輸出絕對平滑(如對下游嚴格保護)→ Leaky Bucket。
- 要簡單且容忍邊界誤差 → Fixed Window。
- 要精準又不太耗記憶體 → Sliding Window Counter。
🎤 面試官追問:「固定視窗的問題是什麼?」(視窗交界:59 秒 100 次 + 下一分鐘 0 秒 100 次 = 1 秒內放行 200 次。滑動視窗或令牌桶可緩解。)「Token Bucket 怎麼允許突發?」(桶裡攢的令牌可一次用掉,所以容許短暫超過平均速率。)
Step 3 — 分散式實作(Redis)⭐
單機用記憶體計數即可;多台伺服器要共用計數,靠集中式儲存:
- Redis 當共享計數器:用
INCR+EXPIRE(固定視窗),或用 Lua 腳本/原子操作保證「讀-改-寫」不發生競態。 - 競態條件:多台同時讀到 99、各自 +1,可能超賣——所以要用 原子操作(Redis INCR 本身原子、或 Lua 包成單一原子腳本)。
- 效能 vs 一致性:嚴格一致→每次都打 Redis(多一次網路往返);可放寬→本機先擋明顯超標、再定期同步。
🎤 面試官追問:「分散式下計數怎麼避免競態?」(Redis 原子 INCR 或 Lua 腳本;別在應用層做「先讀再寫」。)「Redis 掛了怎麼辦?」(fail-open(放行,保可用性)還是 fail-close(擋住,保安全)——依業務取捨;通常限流偏 fail-open,避免限流器自己變成故障點。)
二、設計短網址 TinyURL
Step 1 — 釐清需求
- 核心功能:① 給長網址 → 回短網址;② 訪問短網址 → 重導向到長網址。
- 規模估算:寫少讀多(典型 讀:寫 ≈ 100:1);假設一天千萬筆新短址、QPS 估算決定快取與分片。
- 短碼長度:用 base62(a-z A-Z 0-9);7 碼 ≈ 62⁷ ≈ 3.5 兆,夠用很久。
- 加分需求:自訂別名、過期時間、點擊統計。
🎤 面試官追問:「短碼要多短?怎麼估長度?」(用 base62 算容量:算出要支撐多少筆 URL,反推位數——62⁷ 已是兆級。)
Step 2 — 發號:怎麼產生唯一短碼 ⭐
| 方法 | 原理 | 優點 | 缺點 |
|---|---|---|---|
| 計數器 + Base62 | 全域遞增 ID → 轉成 base62 | 不碰撞、短 | 短碼可預測(連號);發號器要分散式(如號段/Snowflake) |
| 雜湊(MD5/SHA)取前幾碼 | hash(長網址) 取前 7 碼 | 簡單、相同網址同碼 | 可能碰撞(要偵測 + 重試);無序 |
| 預先產生 KGS Key Generation Service | 離線預生一大批不重複 key,用時取一個 | 線上零碰撞、零計算 | 要額外服務管理 key 池 |
怎麼選:
- 不想碰撞、可接受連號 → 計數器 + base62(搭分散式發號)。
- 要短碼不可預測 → 雜湊或 KGS(KGS 最穩,業界常用)。
- 自訂別名 → 另存一張表,寫入前查重。
🎤 面試官追問:「雜湊會碰撞怎麼處理?」(產生後查 DB 是否已存在,碰撞就加鹽/取更多位元重試。)「計數器發號在分散式下怎麼避免單點?」(號段預取(每台拿一段 ID 範圍)或 Snowflake 式分散式 ID。)
Step 3 — 儲存與擴展(讀多寫少)⭐
- 儲存:本質是 key-value(短碼 → 長網址 + metadata),用 NoSQL(如 DynamoDB/Cassandra)或分片的 SQL。
- 分片:資料量大 → 按短碼 hash 分片(consistent hashing)。
- 快取(重點,因讀多寫少):熱門短址放 Redis / CDN,大部分讀取在快取命中,DB 壓力大降。用 LRU 淘汰。
- 架構:使用者 → LB → 應用層 → 先查快取 → miss 再查 DB → 回填快取。
🎤 面試官追問:「讀:寫 100:1,架構重點在哪?」(快取——把絕大多數讀擋在 DB 前;參考通用基本功那篇。)
Step 4 — 重導向:301 vs 302 ⭐
訪問短網址時回哪種重導向,是這題的經典考點:
- 301 永久重導向:瀏覽器會快取,下次直接跳、不再經過你的伺服器 → 省伺服器負載,但收不到點擊統計。
- 302 臨時重導向:每次都會回到你的伺服器再轉址 → 能收集點擊/分析數據,但伺服器負載較高。
怎麼選:要做點擊分析(多數短網址服務的賣點)→ 用 302;純粹想省負載、不在乎統計 → 301。
🎤 面試官追問:「為什麼短網址服務多用 302 而非 301?」(302 每次都經過伺服器,才能記錄點擊數據;301 被瀏覽器快取後你就「看不到」流量了。)
兩題收束對照
| 限流器 Rate Limiter | 短網址 TinyURL | |
|---|---|---|
| 核心考點 | 四種演算法取捨 | 發號策略 + 讀多寫少 |
| 關鍵元件 | Redis 原子計數 | KV 儲存 + 快取 |
| 經典追問 | 固定視窗邊界問題、fail-open/close | 301 vs 302、碰撞處理 |
| 最佳預設 | Token Bucket(允許突發) | KGS 或 counter+base62 + 重快取 |
回答框架口訣
- 限流器:釐清「限誰、限多少、超過怎樣」→ 講四種演算法取捨(重點 Token Bucket 允許突發、固定視窗有邊界問題)→ 分散式用 Redis 原子操作 → 收在 fail-open/close 取捨。
- 短網址:估規模(讀多寫少)→ 發號(counter+base62 / hash / KGS 取捨)→ KV 儲存 + 分片 + 重快取 → 收在 301 vs 302(要統計用 302)。
這兩題雖小,卻把系統設計的核心肌肉都練到了:演算法取捨、原子性與競態、發號與唯一性、讀多寫少的快取、以及一個看似細節卻很考人的取捨(301/302)。 練熟它們,大型題的基礎就穩了。