API 限流:算法与实现

Security2026-09-20TryQuickToolBox

你的 API 正遭受攻击。不是来自复杂的黑客,而是来自一个简单的脚本,它每秒数千次地冲击你的端点,消耗资源并降低合法用户的服务质量。如果没有限流,单个行为不当的客户端就可能拖垮整个应用。本指南解释核心算法,并展示在 Node.js 和 Nginx 中的实用实现,以保护你的 API。

为什么限流很重要

限流控制客户端在给定时间窗口内可以发起的请求数量。它是你抵御以下威胁的第一道防线:

除了安全性,限流还能确保公平使用,并帮助你执行业务规则,例如分层定价计划。

限流算法

四种算法主导了 API 限流。每种算法在准确性、内存使用和突发处理方面都有权衡。

1. 固定窗口

在固定的时间间隔内计数请求(例如,每分钟 100 个请求)。当窗口重置时,计数器重置。

2. 滑动窗口

跟踪每个请求的时间戳,并计算最近 N 秒内有多少个请求。这可以平滑突发。

3. 令牌桶

一个桶存放令牌。令牌以固定速率添加。每个请求消耗一个令牌。如果桶为空,请求被拒绝。

4. 漏桶

请求进入队列(桶),并以恒定速率处理。如果队列已满,请求被丢弃。

算法突发处理内存使用场景
固定窗口差低简单 API
滑动窗口好中通用
令牌桶优秀中允许突发的 API
漏桶无中平滑流量

在 Node.js 中实现限流

我们将使用 Express 和 Redis 实现一个令牌桶限流器,用于分布式状态。当你有多个服务器实例时,Redis 是必不可少的。

第 1 步:安装依赖

npm install express redis

第 2 步:创建限流中间件

const redis = require('redis');
const client = redis.createClient();

async function tokenBucketLimiter(req, res, next) {
  const key = `rate_limit:${req.ip}`;
  const capacity = 10; // 最大令牌数
  const refillRate = 1; // 每秒令牌数

  const now = Date.now();
  const data = await client.hGetAll(key);
  let tokens = data.tokens ? parseFloat(data.tokens) : capacity;
  let lastRefill = data.lastRefill ? parseInt(data.lastRefill) : now;

  // 根据经过的时间重新填充令牌
  const elapsed = (now - lastRefill) / 1000;
  tokens = Math.min(capacity, tokens + elapsed * refillRate);

  if (tokens < 1) {
    return res.status(429).json({ error: 'Too many requests' });
  }

  tokens -= 1;
  await client.hSet(key, {
    tokens: tokens.toString(),
    lastRefill: now.toString()
  });
  await client.expire(key, 60); // 自动清理

  next();
}

app.use(tokenBucketLimiter);

这个中间件原子地检查和更新令牌计数。在生产环境中,使用 Redis 事务或 Lua 脚本以避免竞态条件。

在 Nginx 中实现限流

Nginx 通过 limit_req 模块提供内置限流。它使用漏桶算法。

第 1 步:定义限流区域

在 nginx.conf 的 http 块中:

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

这会创建一个名为 api 的 10MB 区域,允许每个 IP 每秒 10 个请求。

第 2 步:应用限制

在你的 location 块中:

location /api/ {
    limit_req zone=api burst=20 nodelay;
    proxy_pass http://backend;
}

burst=20 允许最多 20 个请求的短时突发。nodelay 立即处理突发请求,而不是排队。

API 限流的最佳实践

常见问题

限流和节流的区别是什么?

限流阻止超过阈值的请求,而节流则减慢它们(例如,通过排队或延迟)。限流是二元的;节流是渐进的。

我应该使用哪种限流算法?

对于大多数 API,令牌桶提供了良好的平衡:它允许突发,但强制执行平均速率。如果需要严格的平滑,使用漏桶。为了简单,固定窗口适用于低流量 API。

如何处理已认证用户与匿名用户的限流?

对匿名用户(例如,按 IP)应用更严格的限制,对已认证用户(例如,按 API 密钥)应用更宽松的限制。你还可以根据订阅计划实现分层限制。

准备好分析你的 API 流量了吗?使用我们的 Nginx 日志分析器 来解析日志,发现限流违规,并优化你的阈值。