API 速率限制專家
使用 Token Bucket、Sliding Window 與 Fixed Window 演算法,搭配 Redis 分散式計數器,實作公平且高效的速率限制。
啟用觸發條件
啟用於:「速率限制」、「節流」、「Token Bucket」、「Sliding Window」、「API 濫用」、「DDoS 防護」、「配額管理」、「429 Too Many Requests」、「請求限制」
不適用於: API 閘道器設定 → api-gateway-reverse-proxy-expert | 快取策略 → cache-strategy-invalidation-expert | WAF/防火牆規則 → 相關安全技能
快速起步
- 定義限制 — 根據 API Key、IP 或用戶層級設定每分鐘請求數
- 選擇演算法 — Sliding Window Log(精確)、Token Bucket(允許突發)、Fixed Window(簡單)
- 使用 Redis — 使用
MULTI/EXEC或 Lua 腳本進行原子操作,實現分散式速率限制 - 回傳正確標頭 —
X-RateLimit-Limit、X-RateLimit-Remaining、Retry-After - 區分層級 — 免費版/專業版/企業版擁有不同限制
核心能力
| 領域 | 技術 |
|---|---|
| 演算法 | Token Bucket、Sliding Window Log、Sliding Window Counter、Fixed Window |
| 儲存 | Redis 7.4+、Valkey、DragonflyDB、記憶體內(單節點) |
| 函式庫 | rate-limiter-flexible、@upstash/ratelimit、express-rate-limit |
| 閘道器外掛 | Kong rate-limiting、Nginx limit_req、Traefik ratelimit |
| 標準 | RFC 6585(429)、RateLimit 標頭(draft-ietf-httpapi-ratelimit) |
架構模式
Sliding Window Counter(Redis Lua)
-- Redis Lua 腳本:Sliding Window 速率限制
-- KEYS[1] = 速率限制鍵
-- ARGV[1] = 視窗大小(秒)
-- ARGV[2] = 最大請求數
-- ARGV[3] = 目前時間戳
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
-- 移除已過期的項目
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- 計算目前視窗內的數量
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. '-' .. math.random(1000000))
redis.call('EXPIRE', key, window)
return {1, limit - count - 1} -- 允許,剩餘次數
else
return {0, 0} -- 拒絕,剩餘 0 次
end
多層級速率限制
請求 → IP 速率限制(100/分鐘)
│ 通過
↓
驗證檢查 → API Key 速率限制(依層級)
│ 免費版:60/分鐘
│ 專業版:600/分鐘
│ 企業版:6000/分鐘
↓ 通過
端點速率限制(依路由)
│ POST /upload:10/分鐘
│ GET /search:120/分鐘
↓ 通過
處理請求
回應標頭(IETF 草案標準)
// 中介軟體:附加速率限制標頭
function rateLimitHeaders(limit: number, remaining: number, resetAt: number) {
return {
'RateLimit-Limit': limit.toString(),
'RateLimit-Remaining': Math.max(0, remaining).toString(),
'RateLimit-Reset': Math.ceil((resetAt - Date.now()) / 1000).toString(),
};
}
// 在 429 回應時:
res.status(429).set({
...rateLimitHeaders(limit, 0, resetAt),
'Retry-After': Math.ceil((resetAt - Date.now()) / 1000).toString(),
}).json({ error: 'Too Many Requests', retryAfter: resetAt });
反模式
- 在分散式系統中使用記憶體內計數器 — 速率限制必須跨實例共享;請使用 Redis 或同等解決方案
- 缺少 Retry-After 標頭 — 未附帶
Retry-After的 429 回應會迫使客戶端猜測,導致驚群效應 - 僅基於 IP 的速率限制 — 僅 IP 限制會遺漏已驗證的濫用行為,並懲罰共享 IP(NAT、VPN);應與 API Key 限制結合
- 無突發能力的硬性截止 — Token Bucket 可在維持平均速率的同時允許小突發;純 Fixed Window 過於僵化
- 未對內部 API 設定限制 — 行為異常的內部服務可能導致資料庫連鎖故障;務必加以限制
品質檢查清單
- 速率限制使用分散式儲存(Redis/Valkey),而非記憶體內
- 每個回應都附帶
RateLimit-Limit、RateLimit-Remaining、RateLimit-Reset標頭 - 429 回應附帶
Retry-After標頭 - 根據驗證層級(免費/專業/企業)設定不同的限制
- 對昂貴操作(上傳、搜尋)設定每個端點的限制
- 使用 Lua 腳本或原子性管線進行計數器遞增(無競爭條件)
- 速率限制鍵包含身分(API Key)和作用域(端點)
- 監控儀表板顯示速率限制命中次數、拒絕次數與最高使用量
- 優雅降級:在可能的情況下,優先佇列或減速,而非直接拒絕