API 限流設計(Rate Limiting)

是什麼?

Rate Limiting 是限制客戶端在一定時間內能發送的請求數量,保護 API 不被惡意攻擊或意外過載。常見方式包含固定窗口、滑動窗口、Token Bucket 和 Leaky Bucket 演算法。

ℹ️為什麼需要限流

沒有限流的 API:一個失控的客戶端每秒發 10,000 次請求就能拖垮整個服務。限流是 API 安全和穩定性的基礎防線

核心觀念

常見誤區

⚠️常見誤區

  • 只靠 IP 限流:同一個 NAT 後可能有成百上千個使用者共用一個 IP,純 IP 限流會誤傷正常用戶。應結合 API Key / User ID 限流
  • 限流後只回 429 不附資訊:必須告訴客戶端何時可以重試(Retry-After Header),否則客戶端只能盲目重試
  • 分散式環境用本地計數器:多台伺服器各自計數,總量會超標。必須用集中式存儲(如 Redis)

流程/步驟

1

辨識限流維度

決定用 IP、User ID、API Key 或組合來識別客戶端

2

選擇演算法

簡單場景用 Fixed Window,需要平滑限流用 Token Bucket

3

設定配額

根據伺服器容量和業務需求設定每分鐘/每小時上限

4

實作計數器

用 Redis 或記憶體快取儲存計數,設定 TTL 自動過期

5

回傳限流資訊

加入 Rate Limit Headers,超限回傳 429 + Retry-After

流程解讀:先決定限流的「對象」(誰被限)和「演算法」(怎麼限),然後根據伺服器承載力設定合理配額。計數器必須用集中式儲存(如 Redis)確保分散式環境的一致性。回應中附帶限流 Header 讓客戶端自我調節。

程式碼範例

C# 版本

csharp
// 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 版本

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 版本

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"}

架構圖/概念圖

Client
request
Rate Limiter (Middleware)
check/increment
Redis (Counter)
API Handler
429 Too Many Requests

每個請求先經過 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 | 恆定速率 | 穩定寫入場景 |

你可能也想看

GraphQL 入門API 文件自動化

按 ← → 鍵切換課程