具代表性的面試主題

系統設計面試:如何設計分散式限流服務?

系統設計困難
OfferCC 編輯團隊發佈 更新

題幹

多個 API 共用一個叢集,流量突增時需要按租戶、使用者和介面限流。你會如何設計限流服務,並處理多節點一致性、故障和策略變更?

題幹與適用場景

題目考察能否把「每分鐘 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 強調平滑輸出;滑動視窗直觀但狀態成本較高。共享狀態存入 Redis 時,在原子腳本內使用 Redis TIME,讓所有節點讀取同一權威時間,並將負的 elapsed 鉗為 0;同時說明過期 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 偽代碼

text
now = redis_time()
state = load(key) or {tokens: burst, at: now}
elapsed = max(0, 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 bucketAPI 突發流量需要共享原子狀態
本地限流低延遲保護多節點額度不精確
區域配額多區域可用性全域公平存在誤差
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 計算,隨後按租戶或百分比啟用,比較拒絕量、轉化和後端負載;策略帶版本方便回滾。

公開來源

同類題目

相關面試工具

用 Solve 整理系統設計回答

從澄清需求開始,展開規模、架構、元件選擇和取捨。

查看工具