System Design·2026年5月14日·9 分鐘閱讀

限流實戰:令牌桶、漏桶,以及我在 API 上真正做的事

L
Louis Wu

後端工程師,為對外 API 與高併發系統設計限流機制。

任何對外的 API 都會遇到這個問題:有人(不管是惡意攻擊、寫壞的程式、還是爬蟲)瘋狂打你的服務。沒有限流,一個失控的來源就能把你的系統資源吃光,害到所有其他正常使用者。這篇講限流的幾個常見演算法,以及我在實務上真正怎麼做。

為什麼一定要限流

限流要保護的,不只是擋惡意攻擊,更是保護系統不被任何單一來源拖垮

  • 擋掉惡意的暴力請求、刷接口
  • 防止一個寫壞的客戶端(無限迴圈狂打)害到大家
  • 保護下游:你的資料庫、第三方 API 都有它的極限,限流讓流量維持在它們扛得住的範圍
  • 公平:避免少數重度使用者把資源全佔走

幾個常見演算法

固定視窗計數 最簡單:在固定的一段時間(例如每分鐘)內數請求次數,超過就擋。實作簡單,但有個明顯的缺陷——**邊界突刺**:如果允許每分鐘 100 次,有人可以在這一分鐘的最後一秒打 100 次、下一分鐘的第一秒再打 100 次,兩秒內就 200 次。

滑動視窗 為了解決邊界突刺,滑動視窗看的是「過去 N 秒」這個一直在移動的區間,而不是切死的整分鐘。比較平滑、比較準,但實作和儲存成本高一些。

令牌桶(token bucket) 我最常用的。想像一個桶,以固定速率往裡面放令牌;每個請求要消耗一個令牌,桶空了就擋。它的好處是**允許一定程度的突發**——桶裡平常累積的令牌,可以讓短暫的尖峰通過,同時又把長期的平均速率限制住。這很符合真實流量「平常平穩、偶爾一陣」的樣子。

漏桶(leaky bucket) 請求進到一個桶裡,以固定速率「漏」出去處理。它強調的是**輸出的平穩**——不管進來多突然,處理速率永遠固定。適合需要保護下游、要求穩定處理節奏的場景。

令牌桶和漏桶常被一起講,差別在:令牌桶允許突發、限制平均;漏桶強制平穩輸出。看你要保護的是「平均量」還是「下游的穩定節奏」。

實務上我真正做的事

限流狀態要集中 如果你有多台機器,限流的計數絕不能各機器各算自己的——那等於限流被機器數放大了。要放在一個集中的地方(我通常用 Redis),多台機器共用同一份計數,限流才準。前面寫 Redis 用法時也提過這點。

在閘道層擋,別讓流量進到核心 限流要做在越外層越好——API 閘道或入口,在請求真正進到你昂貴的業務邏輯之前就擋掉。讓被擋的請求用最便宜的方式被拒絕。

回應要明確、要友善 被限流時,回標準的 429(Too Many Requests)狀態碼,並且最好告訴對方「過多久可以再試」(Retry-After)。一個好的客戶端會看這個資訊乖乖退讓,而不是繼續猛打。

分層限流 我會依不同維度設不同的限制:每個使用者、每個 IP、每個 API key、甚至特定昂貴端點單獨更嚴。一刀切的單一限制往往不夠細緻。

小結

限流是任何正式對外服務的基本保護:

  • 它保護系統不被任何單一來源拖垮,也保護下游
  • 固定視窗簡單但有邊界突刺;滑動視窗較平滑;令牌桶允許突發、限制平均;漏桶強制平穩輸出
  • 實務上:限流狀態集中(用 Redis)、在閘道層擋、回 429 加 Retry-After、依不同維度分層限流

限流不是上線後才補的東西。一個沒有限流的對外 API,等於把系統的穩定性交給了「沒人來搞你」的運氣。

#限流#Rate Limiting#API#高併發#系統設計

相關文章