為什麼需要限流?
沒有限流的系統就像沒有閥門的水管:正常流量時運作良好, 但流量突然暴增時,整個系統可能直接崩潰。
有 vs 沒有限流
三種需要限流的場景
防止濫用
防止惡意用戶大量爬取資料、暴力破解密碼、或透過 API 進行 DDoS 攻擊。
保護後端
突發流量(秒殺活動、新聞爆紅)不讓 DB、下游微服務、第三方 API 被打垮。
公平分配
確保每個用戶都有合理的 quota,防止少數高頻用戶消耗大部分資源,影響其他人。
類比:捷運站閘門
限流就像捷運站的閘門。正常流量下大家都能快速刷卡進站。 尖峰時期閘門控制進站速度,讓站內不至於擁擠到危險。 如果超過承載量就暫時關閘(429 Too Many Requests), 等人潮散去再開放。閘門的目的不是阻止乘客,而是讓系統有序運作。
四種限流演算法
面試最常被問到的就是這四種演算法,每種都有其適用場景與缺點。 理解原理才能在面試和實際設計中做出正確選擇。
1. Fixed Window(固定視窗)
最簡單,但有邊界漏洞
# 每分鐘允許 100 次請求
0:00 ─ 0:59 → 計數器(用完就拒絕)
1:00 ─ 1:59 → 計數器重置
# 問題:視窗邊界可以打 2 倍請求
0:59 打 100 次 + 1:00 打 100 次 = 200 次/2秒
固定視窗計數器在視窗切換瞬間有明顯漏洞,適合對精確度要求不高的場景(如每日 API 配額)。
2. Sliding Window(滑動視窗)
更精確,但儲存成本較高
# 任意時刻往前推 1 分鐘內,最多 100 次請求
t=1:30 → 統計 0:30 ~ 1:30 的請求數
t=1:45 → 統計 0:45 ~ 1:45 的請求數
# 沒有邊界漏洞,任何 60 秒視窗都不超過 100 次
# 實作:用 Redis Sorted Set 儲存每次請求的時間戳
每次請求都需要記錄時間戳(用 Redis Sorted Set),儲存成本隨請求量線性增加。 Section 4 的 Redis 實作就是基於滑動視窗。
3. Token Bucket(令牌桶)
最常用,允許突發流量
# Token Bucket 運作邏輯
桶容量:100 個令牌(最多儲存 100 個)
補充速率:每秒 10 個令牌
請求進來 → 從桶取 1 個令牌
├─ 有令牌 → 允許請求 ✓
└─ 桶空了 → 429 Too Many Requests ✗
# 桶滿了時:可短暫以 100 req/s 高速處理(突發)
# 長期速率:受補充速率控制(10 req/s)
Token Bucket 的最大優點是允許合理的突發流量(Burst)。 用戶偶爾的批次操作可以被吸收,但長期平均速率仍然受控。 AWS API Gateway、Stripe API 都採用類似機制。
4. Leaky Bucket(漏桶)
平滑輸出,保護下游
# Leaky Bucket 運作邏輯
請求進來 → 放入佇列(桶)
├─ 佇列未滿 → 排隊等待
└─ 佇列已滿 → 429 丟棄請求
以固定速率(leak rate)從佇列取出並處理
→ 無論流入速率多快,流出一定是平滑的
Leaky Bucket 強制輸出速率恆定,非常適合保護下游系統(DB、第三方 API)。 缺點是突發流量會在佇列中等待,可能增加延遲;適合後台批次處理,不適合需要低延遲的 API。
快速選型指南
Token Bucket
需要允許突發、對用戶友善的 API 限流
Sliding Window
需要精確控制任意時間段的請求數
Leaky Bucket
需要保護下游系統,輸出必須平滑
Fixed Window
每日/每月 API 配額,精確度要求低
Node.js 實作(Token Bucket)
先從單機記憶體實作開始理解 Token Bucket 的核心邏輯, 再到 Section 4 升級到 Redis 分散式版本。
Redis 分散式限流
單機記憶體的致命缺點
Section 3 的 TokenBucket 把狀態存在應用層記憶體中。 當你有多台 API Server 時(負載均衡)每台各自計數, 同一個 IP 可以打每台伺服器各 100 次,實際通過的請求是設定值的 N 倍。 解法:用 Redis 作為共享狀態存儲,讓所有 Server 讀寫同一個計數器。
限流策略設計
不同端點有不同的安全需求和資源成本,不應該套用相同的限流策略。 以下是一個典型電商/內容平台的限流設計參考。
各端點限流策略建議
| 端點 | 限流策略 | 限流粒度 | 原因 |
|---|---|---|---|
| GET /api/products | 1000 req / min | IP | 讀取操作,可寬鬆;有 CDN 快取輔助 |
| POST /api/login | 5 req / min | IP | 防暴力破解密碼,需要非常嚴格 |
| POST /api/register | 3 req / hour | IP | 防止批量假帳號建立 |
| POST /api/comment | 10 req / min | User ID | 防洗版、防垃圾留言 |
| GET /api/export | 1 req / min | User ID | 資源密集,產生大型 CSV/Excel |
| POST /api/ai-generate | 10 req / day | User ID | LLM API 成本高,按日控制配額 |
| POST /api/payment | 3 req / min | User ID | 防止重複扣款,配合冪等性設計 |
限流的分層架構
生產環境的限流不是只在應用層做一層,而是多層防禦:
第一層:CDN / WAF
按 IP 限流,防 DDoS 和大規模掃描。Cloudflare、AWS WAF 都有內建規則。這層的限制最寬鬆,但可以處理每秒百萬級的請求。
第二層:API Gateway
按 API Key 或 Client ID 限流,實現商業配額(如免費用戶 1000 req/day,付費用戶 10000 req/day)。AWS API Gateway、Kong、Nginx 都支援。
第三層:應用層
按用戶、功能、IP 組合限流,實現細粒度的業務規則(如每個用戶每天最多發 10 篇文章)。這層最靈活,但只保護應用層以下。
處理被限流的請求:客戶端設計
限流不只是伺服器端的事。好的客戶端設計能讓系統在被限流時自動恢復, 而不是直接報錯給用戶。核心概念是 Retry with Backoff。
好的限流客戶端行為
- ✓讀取 Retry-After header,等待建議時間後重試
- ✓使用 Exponential Backoff,越重試等越久
- ✓加入 Jitter,避免 Thundering Herd
- ✓讀取 X-RateLimit-Remaining,提前降速
- ✓在 UI 顯示「稍後再試」而非直接報錯
常見的錯誤行為
- ✗收到 429 立即重試(讓問題更嚴重)
- ✗固定間隔重試(沒有 Jitter,全部同時打)
- ✗忽略 Retry-After header
- ✗無限重試(耗盡資源)
- ✗對 404 也重試(沒有判斷錯誤類型)