API 中的 Rate Limiting:防止滥用的保护机制

Rate limiting 是保护 API 免受滥用、过度资源消耗和拒绝服务攻击的一项基本技术。适当的实现可确保合法用户的可用性,同时阻止恶意或过度的行为。

为什么要实施 Rate Limiting?

  • 防止 DDoS:缓解拒绝服务攻击
  • 防止 Scraping:增加自动化数据提取的难度
  • 控制成本:避免过度消耗计算资源
  • 保证服务质量:公平分配资源
  • 防止 Brute Force:限制身份验证尝试次数

Rate Limiting 算法

1. Token Bucket

一种维护随时间补充的令牌"桶"的算法:

      class TokenBucket {
      constructor(capacity, refillRate) {
      this.capacity = capacity;        // Capacidade máxima do balde
      this.tokens = capacity;          // Tokens disponíveis
      this.refillRate = refillRate;    // Tokens por segundo
      this.lastRefill = Date.now();
      }
      tryConsume(tokens = 1) {
      this.refill();
      if (this.tokens >= tokens) {
      this.tokens -= tokens;
      return true;  // Requisição permitida
      }
      return false;   // Rate limit excedido
      }
      refill() {
      const now = Date.now();
      const timePassed = (now - this.lastRefill) / 1000;
      const tokensToAdd = timePassed * this.refillRate;
      this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
      this.lastRefill = now;
      }
      }
      // Uso: 100 requisições máximo, recarrega 10/segundo
      const bucket = new TokenBucket(100, 10);
      

优点:允许受控的突发流量,平滑流量

缺点:实现更为复杂

2. Leaky Bucket

以恒定速率处理请求,就像水从桶中漏出一样:

  • 请求进入桶中
  • 以固定速率处理
  • 多余的部分溢出(被拒绝)
  • 保证均匀的输出

3. Fixed Window

在固定时间窗口内对请求进行计数:

      class FixedWindowRateLimiter {
      constructor(maxRequests, windowMs) {
      this.maxRequests = maxRequests;
      this.windowMs = windowMs;
      this.requests = new Map();
      }
      isAllowed(userId) {
      const now = Date.now();
      const windowStart = Math.floor(now / this.windowMs) * this.windowMs;
      const key = \`\$:\$\`;
      const count = this.requests.get(key) || 0;
      if (count < this.maxRequests) {
      this.requests.set(key, count + 1);
      return true;
      }
      return false;
      }
      }
      // 100 requisições por hora
      const limiter = new FixedWindowRateLimiter(100, 60 * 60 * 1000);
      

问题:在窗口边缘允许达到限制的 2 倍

4. Sliding Window Log

维护请求时间戳的日志:

  • 存储每个请求的时间戳
  • 移除窗口外的请求
  • 比 Fixed Window 更精确
  • 内存消耗更高

5. Sliding Window Counter

将 Fixed Window 与平滑处理相结合:

      // Calcula uma média ponderada entre janelas atual e anterior
      const currentWindowCount = getCurrentWindowCount(userId);
      const previousWindowCount = getPreviousWindowCount(userId);
      const percentageInCurrentWindow = (now - currentWindowStart) / windowSize;
      const estimatedCount =
      previousWindowCount * (1 - percentageInCurrentWindow) +
      currentWindowCount;
      return estimatedCount < maxRequests;
      

实践实现

使用 Redis(推荐用于生产环境)

      import Redis from 'ioredis';
      const redis = new Redis();
      async function checkRateLimit(userId, maxRequests = 100, windowSeconds = 60) {
      const key = \`rate_limit:\$\`;
      const now = Date.now();
      const windowStart = now - (windowSeconds * 1000);
      // Remover requisições antigas
      await redis.zremrangebyscore(key, 0, windowStart);
      // Contar requisições na janela
      const requestCount = await redis.zcard(key);
      if (requestCount < maxRequests) {
      // Adicionar nova requisição
      await redis.zadd(key, now, \`\$-\${Math.random()}\`);
      await redis.expire(key, windowSeconds);
      return { allowed: true, remaining: maxRequests - requestCount - 1 };
      }
      return { allowed: false, remaining: 0 };
      }
      // Middleware Express
      app.use(async (req, res, next) => {
      const userId = req.user?.id || req.ip;
      const result = await checkRateLimit(userId);
      res.set({
      'X-RateLimit-Limit': 100,
      'X-RateLimit-Remaining': result.remaining,
      'X-RateLimit-Reset': new Date(Date.now() + 60000).toISOString()
      });
      if (!result.allowed) {
      return res.status(429).json({
      error: 'Too Many Requests',
      retryAfter: 60
      });
      }
      next();
      });
      

常用库

  • express-rate-limit:用于 Express.js 的 Middleware
  • rate-limiter-flexible:支持多种 backends(Redis、Memcached、MySQL)
  • Kong Rate Limiting:用于 API Gateway 的 Plugin
  • AWS API Gateway:原生 rate limiting

高级策略

分层 Rate Limiting

  • 全局:API 的总限制(例如:1M req/min)
  • 按用户:个人限制(例如:1000 req/min)
  • 按 Endpoint:特定限制(login:5 req/min)
  • 按 IP:针对滥用的额外保护

动态 Rate Limiting

  • 根据系统负载调整限制
  • 为高级用户提高限制
  • 在事件期间降低限制

Whitelisting 和 Blacklisting

  • 豁免受信任的 IP/用户
  • 永久阻止已知攻击者
  • 实施信誉系统

最佳实践

  • 返回信息性标头(X-RateLimit-*)
  • 使用 HTTP 状态码 429(Too Many Requests)
  • 包含 Retry-After 标头
  • 在 API 中清晰地记录限制
  • 在客户端实现指数退避
  • 监控 rate limiting 指标
  • 对异常模式发出警报
  • 在投入生产前测试限制

监控工具

  • Grafana + Prometheus:可视化 rate limiting 指标
  • Datadog:监控和告警
  • CloudWatch:用于 AWS 上的 API
  • New Relic:支持 rate limiting 的 APM