Node.js 服务端如何使用令牌桶算法限制异步并发请求频率?

文章导读
Node.js 服务端通常通过中间件或专用库实现令牌桶算法,适合保护 API 接口免受突发流量冲击。
📋 目录
  1. 快速处理思路
  2. 为什么会这样
  3. 分步处理
  4. 怎么验证是否生效
  5. 常见坑
  6. 常见问题
  7. 参考来源
A A

Node.js 服务端通常通过中间件或专用库实现令牌桶算法,适合保护 API 接口免受突发流量冲击。

单机环境可直接使用内存存储,集群环境必须配合 Redis 等共享存储同步令牌状态,否则限流会失效。

先说结论:在 Node.js 中实现令牌桶限流,优先选用支持分布式存储的成熟库,避免自行实现底层算法导致状态不一致。

  • 适合:API 网关、高频写入接口、防止恶意刷请求场景
  • 先准备:确认服务是单实例还是集群,集群需准备 Redis 实例
  • 验收:通过压力测试工具验证超出阈值后是否返回 429 状态码

快速处理思路

Node.js 没有内置令牌桶模块,需引入第三方库并在中间件层拦截请求。

npm install rate-limiter-flexible
// 或
npm install limiter

代码层面需在路由处理前插入限流中间件,捕获超限异常并返回标准 HTTP 状态码。

为什么会这样

令牌桶算法允许一定程度的突发流量,同时限制长期平均请求速率。

与固定窗口算法不同,令牌桶以固定速率向桶中放入令牌,请求消耗令牌,桶满则丢弃多余令牌。这种机制既能防止瞬时流量打垮服务,又不会像漏桶算法那样强制平滑所有请求,适合大多数 Web 服务场景。公开资料中没有看到可靠的量化数据表明令牌桶比滑动窗口性能更高,选择主要取决于业务对突发流量的容忍度。

分步处理

第一步:选择限流库并初始化配置。

Node.js 服务端如何使用令牌桶算法限制异步并发请求频率?

推荐使用 rate-limiter-flexible,它支持内存和 Redis 存储切换。初始化时设置 points(令牌数量)和 duration(时间窗口秒数)。

const { RateLimiterNode } = require('rate-limiter-flexible');

const rateLimiter = new RateLimiterNode({
  points: 10, // 每秒允许 10 个请求
  duration: 1, // 时间窗口 1 秒
});

第二步:在 Express 或 Koa 中间件中调用消耗令牌方法。

需在路由逻辑之前执行 consume 方法,捕获 RateLimiterRes 异常。

app.use(async (req, res, next) => {
  try {
    await rateLimiter.consume(req.ip);
    next();
  } catch (rejRes) {
    res.status(429).send('Too Many Requests');
  }
});

第三步:集群环境切换为 Redis 存储。

若服务部署多实例,需将 RateLimiterNode 改为 RateLimiterRedis,并传入 Redis 客户端实例,确保所有节点共享令牌计数。

怎么验证是否生效

通过自动化脚本连续发送请求,观察响应状态码和响应头。

Node.js 服务端如何使用令牌桶算法限制异步并发请求频率?

使用 autocannonwrk 工具发起超过阈值的请求量,正常请求应返回 200,超限请求应返回 429。检查服务端日志,确认限流库是否记录了 consume 拒绝事件。若使用 Redis,可通过 KEYS 命令查看限流键是否存在且随时间变化。

常见坑

第一,集群环境未共享存储导致限流失效。

每个 Node.js 进程内存独立,若不接 Redis,多实例下总吞吐量会翻倍,无法达到预期限流效果。

第二,限流键值设计不合理导致误伤。

仅用 IP 限流可能影响 NAT 后的正常用户,建议结合用户 ID 或 API Key 作为键值。

第三,未处理限流库本身的异常。

Redis 连接失败时,限流库可能抛出错误,需决定是放行请求还是直接拒绝,避免导致服务不可用。

Node.js 服务端如何使用令牌桶算法限制异步并发请求频率?

常见问题

令牌桶和漏桶算法有什么区别?

令牌桶允许突发流量,漏桶强制恒定速率输出。

令牌桶在桶内有令牌时可瞬间处理多个请求,适合允许短时突发的场景;漏桶不管输入多快,输出速率固定,适合需要严格平滑流量的场景。

必须使用 Redis 吗?

单实例服务不需要,多实例集群必须使用。

单机部署时内存存储性能更好且无网络开销;一旦横向扩展,必须通过 Redis 等外部存储同步计数,否则各节点限流独立计算。

限流会影响接口响应延迟吗?

会引入少量额外延迟,主要来自存储读写开销。

内存存储延迟通常在毫秒级以下,Redis 存储取决于网络往返时间。公开资料中没有看到可靠的量化数据表明具体延迟增加数值,建议在实际环境压测评估。

参考来源

  • npmjs.com, Package: rate-limiter-flexible, URL: https://www.npmjs.com/package/rate-limiter-flexible
  • GitHub, Repository: animir/node-rate-limiter-flexible, URL: https://github.com/animir/node-rate-limiter-flexible
  • Public Algorithm Documentation, Token Bucket Algorithm, 公开资料中没有看到可靠的量化数据