題幹與適用場景
題目考察能否把「每分鐘 N 次」變成可擴展、可營運的控制面與資料面。限流既要保護容量,也要保留公平性和業務優先級;分散式節點與共享狀態會帶來近似誤差。回答應涵蓋策略、演算法、儲存、回應、降級、發布和監控。
面試官考察點
強回答會先釐清限流維度、視窗、突發容量和失敗語意,再比較 token bucket、leaky bucket、fixed/sliding window。它會把策略分發與請求判定分離,說明原子計數、熱點 key、跨區和 Redis 故障處理,並返回 Retry-After。最後要說明 shadow rollout、錯誤預算和繞過保護的稽核。
回答前需要釐清的問題
- 按 API、租戶、使用者、IP 還是組合鍵限流?付費客戶是否有優先級?
- 目標是平均速率、突發容量、並發數,還是同時控制多種資源?
- 超限返回拒絕、排隊、降級,還是允許小比例超發?跨區是否要求嚴格一致?
- 限流器不可用時 fail-open 還是 fail-closed?哪些介面必須保護?
- 策略多久變更一次,是否需要灰度、稽核、回滾和即時生效?
30 秒回答框架
「我會把策略控制面和請求資料面分開。每個請求按租戶、使用者和路由生成規範化 key,資料面用 Redis Lua 或原子腳本執行 token bucket,返回剩餘配額和重試時間。策略按版本快取,本地快取只做快速拒絕,最終判定走共享狀態。共享儲存短暫不可用時,低風險介面按租戶預算 fail-open,高風險介面 fail-closed 並告警。透過 shadow、burn rate、熱點 key 和跨區誤差監控逐步發布。」
分步深入解答
第一步:定義容量與公平性
把容量分成 sustained rate、burst size 和並發上限。租戶額度防止大客戶擠占全域資源,使用者額度防止單租戶內噪聲;關鍵介面可有獨立預算。公平性是業務規則,不應藏在演算法預設值中。
第二步:選擇演算法
Token bucket 允許受控突發,適合 API;leaky bucket 強調平滑輸出;滑動視窗直觀但狀態成本較高。使用單調時間計算 token,不依賴跨節點牆上時鐘,並說明過期 key 清理。
第三步:設計 key 與策略模型
規範化 method、route、tenant、principal 和 region,避免節點生成不同 key。策略包含 limit、burst、scope、優先級、版本、生效時間和 owner。未知策略使用安全基線,不讓客戶端自帶額度。
第四步:實作原子判定
共享儲存一次完成讀取、補充 token、扣減和 TTL 更新。Redis Lua、原子資料庫操作或 sidecar 都可行;關鍵是避免讀後寫競態。回應返回剩餘 token、reset 時間和策略版本。
第五步:處理熱點與多區域
熱門租戶會把單 key 變成熱點。可以分片並由協調器合併,但要量化超發誤差;多區域可採區域配額,非同步彙總全域使用量。嚴格全球一致會犧牲可用性和延遲。
第六步:設計故障與降級
按介面風險選擇 fail-open 或 fail-closed,並設定本地應急預算。策略服務不可用時使用最後版本和短 TTL;共享狀態恢復後不能一次釋放全部積壓。所有繞過和降級寫入稽核事件。
第七步:發布與變更
新策略先 shadow 只記錄不拒絕,評估預計拒絕量;再按租戶或百分比灰度,最後全量。策略版本進入請求日誌,支援回滾;暫時豁免要有 owner 和期限。
第八步:監控效果
監控允許/拒絕率、剩餘配額、p95 判定延遲、熱點 key、儲存錯誤、策略版本、誤拒絕和後端過載,並與 5xx、佇列長度和尾延遲關聯。
原子 token bucket 偽代碼
now = monotonic_time()
state = load(key) or {tokens: burst, at: now}
elapsed = now - state.at
state.tokens = min(burst, state.tokens + elapsed * rate)
allowed = state.tokens >= cost
if allowed:
state.tokens -= cost
state.at = now
save_atomically(key, state, ttl)
return allowed, state.tokens, retry_after(state)設計取捨與邊界
| 選擇 | 適用 | 主要代價 |
|---|---|---|
| Token bucket | API 突發流量 | 需要共享原子狀態 |
| 本地限流 | 低延遲保護 | 多節點額度不精確 |
| 區域配額 | 多區域可用性 | 全域公平存在誤差 |
| Fail-closed | 支付、鑑權等高風險 | 故障擴大可用性影響 |
限流不是佇列,也不能取代容量規劃、熔斷或身份鑑權。排隊適合可等待請求,限流適合資源邊界快速拒絕。
落地計畫與證據
先為一個高流量 API 建立策略和 token bucket 資料面,shadow 一天,再灰度少數租戶。Microsoft Well-Architected 將 throttling 視為過載時的主動控制;DataInterview 與 System Design School 材料都強調演算法、共享狀態和 Retry-After。
試點退出條件
演練突發流量、共享儲存故障、策略回滾和熱點租戶後,服務仍滿足延遲目標;誤拒絕有原因;降級預算有效;所有策略有 owner、版本和稽核記錄。
如何證明收益不是巧合
比較啟用前後後端過載、5xx、p99 延遲、拒絕率、誤拒絕率和儲存成本,並按租戶、路由和區域分層。用容量歸一化流量,避免把低峰誤判為成功。
常見誤區與追問
每個節點本地計數就夠了
節點各自放行額度,切換節點後可能超發。若接受近似要量化誤差;關鍵介面需要共享狀態或區域預算。
只用固定視窗
視窗邊界會允許短時間雙倍突發。可用 token bucket 或滑動視窗,並說明成本。
限流器掛了就全部放行
高風險介面會失去保護。按業務風險設定應急預算、fail-open/closed 和告警。
如何避免誤傷大客戶?
按租戶配置額度和優先級,分離共享池與保底池;臨時豁免必須有期限和稽核。
客戶端應如何重試?
尊重 Retry-After,使用帶抖動退避,不對 429 無限重試。只有冪等請求適合自動重試。
如何灰度新策略?
先 shadow 計算,隨後按租戶或百分比啟用,比較拒絕量、轉化和後端負載;策略帶版本方便回滾。