API 限流設計(Rate Limiting)
是什麼?
Rate Limiting 是限制客戶端在一定時間內能發送的請求數量,保護 API 不被惡意攻擊或意外過載。常見方式包含固定窗口、滑動窗口、Token Bucket 和 Leaky Bucket 演算法。
ℹ️為什麼需要限流
沒有限流的 API:一個失控的客戶端每秒發 10,000 次請求就能拖垮整個服務。限流是 API 安全和穩定性的基礎防線。
核心觀念
- Fixed Window(固定窗口):每個時間窗口(如每分鐘)有固定配額,窗口結束歸零。簡單但有邊界爆量問題(兩個窗口交界處瞬間可達 2 倍配額)
- Sliding Window(滑動窗口):結合固定窗口和滑動日誌,計算最近 N 秒內的請求數。更平滑但計算稍複雜
- Token Bucket(令牌桶):桶中以固定速率生成 Token,每次請求消耗一個。允許短暫的流量突增(burst),用完就限流
- Leaky Bucket(漏桶):請求進入桶中,以固定速率處理。超過桶容量的請求被丟棄。輸出速率恆定
- Rate Limit Headers:回應中附帶
X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset,讓客戶端知道配額狀況
常見誤區
⚠️常見誤區
- 只靠 IP 限流:同一個 NAT 後可能有成百上千個使用者共用一個 IP,純 IP 限流會誤傷正常用戶。應結合 API Key / User ID 限流
- 限流後只回 429 不附資訊:必須告訴客戶端何時可以重試(Retry-After Header),否則客戶端只能盲目重試
- 分散式環境用本地計數器:多台伺服器各自計數,總量會超標。必須用集中式存儲(如 Redis)
流程/步驟
辨識限流維度
決定用 IP、User ID、API Key 或組合來識別客戶端
選擇演算法
簡單場景用 Fixed Window,需要平滑限流用 Token Bucket
設定配額
根據伺服器容量和業務需求設定每分鐘/每小時上限
實作計數器
用 Redis 或記憶體快取儲存計數,設定 TTL 自動過期
回傳限流資訊
加入 Rate Limit Headers,超限回傳 429 + Retry-After
流程解讀:先決定限流的「對象」(誰被限)和「演算法」(怎麼限),然後根據伺服器承載力設定合理配額。計數器必須用集中式儲存(如 Redis)確保分散式環境的一致性。回應中附帶限流 Header 讓客戶端自我調節。
程式碼範例
C# 版本
// ASP.NET Core — 內建 Rate Limiting(.NET 7+)
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = 429;
// Token Bucket 策略
options.AddTokenBucketLimiter("api", limiter =>
{
limiter.TokenLimit = 100; // 桶容量
limiter.ReplenishmentPeriod = TimeSpan.FromMinutes(1);
limiter.TokensPerPeriod = 100; // 每分鐘補充 100 個
limiter.AutoReplenishment = true;
limiter.QueueLimit = 0; // 不排隊,直接拒絕
});
// 依 User ID 限流
options.AddPolicy("per-user", context =>
{
var userId = context.User?.FindFirst("sub")?.Value ?? "anonymous";
return RateLimitPartition.GetTokenBucketLimiter(userId,
_ => new TokenBucketRateLimiterOptions
{
TokenLimit = 50,
ReplenishmentPeriod = TimeSpan.FromMinutes(1),
TokensPerPeriod = 50
});
});
});
app.UseRateLimiter();
[HttpGet]
[EnableRateLimiting("per-user")]
public ActionResult<IEnumerable<UserDto>> GetUsers() => Ok(_users);TypeScript 版本
// Express.js — express-rate-limit
import rateLimit from "express-rate-limit";
// Fixed Window 限流
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 分鐘
max: 100, // 每個 IP 最多 100 次
standardHeaders: true, // 回傳 RateLimit-* Headers
legacyHeaders: false,
message: {
type: "https://api.example.com/errors/rate-limit",
title: "Too Many Requests",
status: 429,
detail: "Rate limit exceeded. Try again later.",
},
keyGenerator: (req) => req.user?.id || req.ip, // 依 User ID 或 IP
});
app.use("/api/", apiLimiter);
// Redis-based(分散式環境)
import RedisStore from "rate-limit-redis";
import Redis from "ioredis";
const limiter = rateLimit({
store: new RedisStore({ sendCommand: (...args) => redis.call(...args) }),
windowMs: 60 * 1000,
max: 100,
});Python 版本
# FastAPI — slowapi
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
@app.get("/api/users")
@limiter.limit("100/minute")
async def get_users(request: Request):
return {"data": await user_service.get_all()}
# 依 User ID 限流
def get_user_id(request: Request) -> str:
token = request.headers.get("authorization", "")
# 解析 token 取得 user_id
return decode_token(token).get("sub", request.client.host)
user_limiter = Limiter(key_func=get_user_id)
@app.get("/api/premium")
@user_limiter.limit("1000/hour")
async def premium_endpoint(request: Request):
return {"data": "premium content"}架構圖/概念圖
每個請求先經過 Rate Limiter Middleware,它向 Redis 查詢並更新計數。未超限的請求放行到 API Handler,超限的直接回傳 429。Redis 作為集中式計數器確保多台伺服器的限流一致。
實戰補充
Q: 如何為不同的 API 設定不同配額?
A: 按重要程度分級。例如:登入 API 每分鐘 10 次(防暴力破解)、讀取 API 每分鐘 100 次、寫入 API 每分鐘 30 次。付費用戶可以有更高的配額。
Q: Token Bucket 和 Leaky Bucket 哪個好?
A: Token Bucket 允許短暫的流量突增(burst),適合正常使用有波動的場景。Leaky Bucket 輸出速率恆定,適合需要穩定處理速率的場景(如寫入資料庫)。大多數 API 限流用 Token Bucket。
理解測驗
🤔 Fixed Window 限流的主要缺點是什麼?
🤔 分散式環境中為什麼不能用本地記憶體做限流計數?
🤔 客戶端收到 429 狀態碼時,哪個 Header 告訴它何時可以重試?
重點整理
💡一句話記住
限流 = API 的保險絲,流量超標就斷開保護系統。 口訣:「Token Bucket 容突發,Redis 管全局」
| 演算法 | 特性 | 適用場景 | |--------|------|---------| | Fixed Window | 簡單但有邊界問題 | 低精度需求 | | Sliding Window | 平滑但稍複雜 | 精確限流 | | Token Bucket | 允許 burst | 大多數 API | | Leaky Bucket | 恆定速率 | 穩定寫入場景 |