express-rate-limit vs ratelimiter vs bottleneck vs limiter vs p-limit vs rate-limiter-flexible
Node.js 限流与并发控制库深度对比
express-rate-limitratelimiterbottlenecklimiterp-limitrate-limiter-flexible类似的npm包:

Node.js 限流与并发控制库深度对比

bottleneckexpress-rate-limitlimiterp-limitrate-limiter-flexibleratelimiter 都是用于在 Node.js 环境中管理请求频率和并发任务的工具,但它们的侧重点截然不同。

p-limit 是一个极简的并发控制库,专注于限制同时执行的 Promise 数量,适合客户端或简单的脚本任务。bottleneck 则是一个功能强大的作业调度器,不仅限制并发,还能智能排队、优先处理任务并自动适应下游服务的速率限制,非常适合调用第三方 API。

在服务端防护方面,express-rate-limit 是 Express 框架的标准中间件,用于防止暴力破解和 DDoS 攻击。rate-limiter-flexible 提供了更高级的架构支持,内置多种存储后端(如 Redis),适合微服务和分布式系统。limiter 是一个老牌库,采用令牌桶算法,但在现代异步场景中略显笨重。而 ratelimiter 目前已被官方标记为弃用(deprecated),不应在新项目中使用。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
express-rate-limit48,471,8663,288153 kB817 天前MIT
ratelimiter161,992723-116 年前MIT
bottleneck02,003-887 年前MIT
limiter01,564158 kB152 年前MIT
p-limit02,91514.9 kB01 个月前MIT
rate-limiter-flexible03,581230 kB92 个月前ISC

Node.js 限流与并发控制:从简单并发到分布式防护

在构建高可用的 Node.js 应用时,控制“速度”至关重要。这通常分为两个场景:一是** outbound(出站),即我们要控制自己调用第三方 API 的频率,避免被封禁;二是 inbound(入站)**,即我们要限制用户对我们的服务器发起请求的频率,防止服务过载。

本文对比的六个包涵盖了这两个场景的不同需求。让我们深入看看它们在实际工程中是如何工作的。

🚦 核心定位:并发控制 vs 速率限制

首先必须明确一个概念:并发控制(Concurrency Control)速率限制(Rate Limiting) 是不同的。

  • 并发控制 关注的是“同一时刻最多有多少个任务在运行”。
  • 速率限制 关注的是“单位时间内最多允许多少个请求”。

p-limit 是典型的并发控制工具,而 express-rate-limitrate-limiter-flexible 则是典型的速率限制工具。bottleneck 则是两者的集大成者。

🏃 出站流量:调用第三方 API 的最佳实践

当你需要调用外部 API(例如抓取数据、发送邮件)时,直接发起请求往往会导致被对方服务器拒绝(HTTP 429)。这时你需要一个能够“排队”并“平滑发送”请求的工具。

1. p-limit:最简单的并发锁

如果你只需要限制同时运行的任务数量,而不关心时间窗口,p-limit 是最轻量的选择。

import pLimit from 'p-limit';

// 限制同时只有 2 个任务运行
const limit = pLimit(2);

const input = [1, 2, 3, 4, 5];

// 映射所有任务,但它们会排队执行
const result = await Promise.all(input.map(async (val) => {
  return limit(() => {
    console.log(`Starting task ${val}`);
    return fetch(`https://api.example.com/data/${val}`);
  });
}));

适用场景:前端图片懒加载、简单的脚本批量处理。它没有内置的延迟机制,只是单纯地暂停启动新任务。

2. bottleneck:智能作业调度器

bottleneck 远不止是限制并发。它能模拟“令牌桶”或“固定窗口”,自动计算何时发送下一个请求,甚至支持优先级队列。

import Bottleneck from 'bottleneck';

// 配置:每 1000ms 最多运行 5 个任务,且任务之间至少间隔 200ms
const limiter = new Bottleneck({
  minTime: 200, 
  maxConcurrent: 5,
  highWater: 100, // 队列长度警告阈值
  strategy: Bottleneck.strategy.OVERFLOW // 队列满时的策略
});

// 提交任务
limiter.schedule(() => fetch('https://api.github.com/users'), { priority: 1 })
  .then(res => console.log('Done'));

// 监听队列状态
limiter.on('highwater', () => {
  console.warn('队列积压过多,考虑降低发送速度');
});

适用场景:调用 Stripe、GitHub、Google Maps 等有严格速率限制的 API。它能保证你的请求曲线是平滑的,而不是突发式的。

3. limiter:老派的令牌桶

limiter 是一个较老的库,它实现了经典的令牌桶算法。虽然功能可靠,但其 API 设计偏向回调风格,对 Promise 的支持不如前两者自然。

import { TokenBucket } from 'limiter';

// 每 1000ms 补充 1 个令牌,桶容量为 5
const bucket = new TokenBucket(1, 1000, null, 5);

bucket.removeTokens(1, (err, remainingTokens) => {
  if (err) throw err;
  // 拿到令牌后执行请求
  fetch('https://api.example.com/data');
  console.log(`剩余令牌: ${remainingTokens}`);
});

适用场景:维护旧项目,或者需要精确控制令牌补充速率且不喜欢 bottleneck 复杂配置的场景。在新项目中,通常推荐 bottleneck

🛡️ 入站流量:保护你的服务器

当面对用户请求时,你需要防止单个 IP 或用户刷爆你的接口。

4. express-rate-limit:Express 的标准卫士

这是 Express 生态中最流行的中间件。它默认使用内存存储,配置简单,足以应对大多数单体应用。

import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 分钟窗口
  max: 100, // 每个 IP 最多 100 次请求
  message: '请求过于频繁,请稍后再试',
  standardHeaders: true, // 返回 RateLimit-* 头
  legacyHeaders: false,
});

app.use('/api/', limiter);

适用场景:标准的 Express 后端,单实例部署。如果你的应用部署在多个实例上且没有共享存储(如 Redis),每个实例会独立计数,可能导致限制不精确。

5. rate-limiter-flexible:分布式系统的利器

当你的应用部署在多个容器或服务器(集群模式)时,内存存储就不再可靠了。rate-limiter-flexible 支持 Redis、Memcached 等多种后端,确保全局限流一致。

import { RateLimiterRedis } from 'rate-limiter-flexible';
import Redis from 'ioredis';

const redisClient = new Redis();

const limiter = new RateLimiterRedis({
  storeClient: redisClient,
  keyPrefix: 'middleware',
  points: 10, // 10 次请求
  duration: 1, // 每 1 秒
});

// 在中间件中使用
app.use(async (req, res, next) => {
  try {
    await limiter.consume(req.ip);
    next();
  } catch (rejRes) {
    res.status(429).send('Too Many Requests');
  }
});

适用场景:微服务架构、高并发系统、需要精确全局限流的场景。它也支持非 Express 环境(如 Koa、NestJS 或纯 HTTP 模块)。

6. ratelimiter:⚠️ 已弃用,请勿使用

ratelimiter 曾经是一个流行的通用限流库,但目前已正式弃用(Deprecated)。其维护者建议所有用户迁移到 rate-limiter-flexible

// ❌ 不要在新项目中这样做
// const Limiter = require('ratelimiter'); 
// 该包不再接收安全更新,且缺乏现代存储适配

建议:如果你正在使用它,请制定计划迁移到 rate-limiter-flexible,以获得更好的性能和安全性。

📊 深度对比总结

特性p-limitbottleneckexpress-rate-limitrate-limiter-flexiblelimiterratelimiter
主要用途并发控制 (Outbound)智能调度 (Outbound)基础限流 (Inbound)分布式限流 (Inbound)令牌桶 (通用)❌ 已弃用
存储后端内存 (无状态)内存 / Redis内存 / 自定义Redis, Memcached, DB内存内存 / Redis
集群支持✅ (需配置)❌ (默认)原生支持
Promise 支持✅ 原生✅ 原生 (schedule)✅ (中间件)✅ (consume)⚠️ 回调为主
优先级队列支持
框架依赖Express无 (框架无关)

💡 架构师建议

  1. 调用第三方 API:毫不犹豫选择 bottleneck。它的 minTimemaxConcurrent 组合能完美模拟大多数 API 的速率限制策略,且内置的队列管理能防止内存溢出。
  2. 简单的脚本/前端任务:使用 p-limit。它体积极小,没有多余依赖,解决“同时只能跑 5 个”这类问题绰绰有余。
  3. Express 单体应用:使用 express-rate-limit。配置最简单,文档最丰富,能满足 90% 的防刷需求。
  4. 微服务/高并发集群:必须使用 rate-limiter-flexible 配合 Redis。只有共享存储才能保证在水平扩展时限流规则依然有效。
  5. 遗留系统:如果遇到 limiter,理解其令牌桶逻辑即可;如果遇到 ratelimiter,请尽快替换。

限流不仅仅是加个锁,它是系统稳定性的基石。选择正确的工具,能让你的应用在流量洪峰中依然稳如泰山。

如何选择: express-rate-limit vs ratelimiter vs bottleneck vs limiter vs p-limit vs rate-limiter-flexible

  • express-rate-limit:

    选择 express-rate-limit 如果你正在构建标准的 Express 应用,并且只需要一个简单的中间件来限制每个 IP 或用户的请求频率以防止滥用。它是上手最快、社区最成熟的 Express 专用解决方案。

  • ratelimiter:

    切勿在新项目中选择 ratelimiter。该包已在 npm 上被官方标记为弃用(deprecated),其功能已被更现代、维护更活跃的 rate-limiter-flexible 所取代,继续使用存在安全和维护风险。

  • bottleneck:

    选择 bottleneck 当你需要与第三方 API(如 GitHub、Stripe)交互,且需要处理复杂的排队逻辑、优先级调度或自动退避策略。它不仅限制并发,还能确保请求以平稳的速率发送,避免触发远程服务的 429 错误。

  • limiter:

    选择 limiter 仅当你维护遗留代码库或需要严格的令牌桶(Token Bucket)算法实现且不依赖 Promise 风格。对于新的异步优先项目,通常有更现代、更易用的替代品。

  • p-limit:

    选择 p-limit 当你只需要在前端或 Node.js 脚本中简单限制同时运行的 Promise 数量(例如限制图片并发加载数),而不需要复杂的排队、存储后端或时间窗口逻辑。

  • rate-limiter-flexible:

    选择 rate-limiter-flexible 当你需要构建高可用的分布式系统,要求限流逻辑与 Redis 等外部存储同步,或者需要在非 Express 环境(如 Koa、NestJS、纯 HTTP 服务器)中复用同一套限流规则。

express-rate-limit的README

express-rate-limit

tests npm version npm downloads license

Basic rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset. Plays nice with express-slow-down and ratelimit-header-parser.

Usage

The full documentation is available on-line.

import { rateLimit } from 'express-rate-limit'

const limiter = rateLimit({
	windowMs: 15 * 60 * 1000, // 15 minutes
	limit: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
	standardHeaders: 'draft-8', // draft-6: `RateLimit-*` headers; draft-7 & draft-8: combined `RateLimit` header
	legacyHeaders: false, // Disable the `X-RateLimit-*` headers.
	ipv6Subnet: 56, // Set to 60 or 64 to be less aggressive, or 52 or 48 to be more aggressive
	// store: ... , // Redis, Memcached, etc. See below.
})

// Apply the rate limiting middleware to all requests.
app.use(limiter)

Data Stores

The rate limiter comes with a built-in memory store, and supports a variety of external data stores.

Configuration

All function options may be async. Click the name for additional info and default values.

OptionTypeRemarks
windowMsnumberHow long to remember requests for, in milliseconds.
limitnumber | functionHow many requests to allow.
messagestring | json | functionResponse to return after limit is reached.
statusCodenumberHTTP status code after limit is reached (default is 429).
handlerfunctionFunction to run after limit is reached (overrides message and statusCode settings, if set).
legacyHeadersbooleanEnable the X-Rate-Limit header.
standardHeaders'draft-6' | 'draft-7' | 'draft-8'Enable the Ratelimit header.
identifierstring | functionName associated with the quota policy enforced by this rate limiter.
storeStoreUse a custom store to share hit counts across multiple nodes.
passOnStoreErrorbooleanAllow (true) or block (false, default) traffic if the store becomes unavailable.
keyGeneratorfunctionIdentify users (defaults to IP address).
ipv6Subnetnumber (32-64) | function | falseHow many bits of IPv6 addresses to use in default keyGenerator
requestPropertyNamestringAdd rate limit info to the req object.
skipfunctionReturn true to bypass the limiter for the given request.
skipSuccessfulRequestsbooleanUncount 1xx/2xx/3xx responses.
skipFailedRequestsbooleanUncount 4xx/5xx responses.
requestWasSuccessfulfunctionUsed by skipSuccessfulRequests and skipFailedRequests.
validateboolean | objectEnable or disable built-in validation checks.
loggerLoggerCustom logger

Thank You


Thanks to Mintlify for hosting the documentation at express-rate-limit.mintlify.app

Create your docs today


And thank you to everyone who's contributed to this project in any way! 🫶

Issues and Contributing

If you encounter a bug or want to see something added/changed, please go ahead and open an issue! If you need help with something, feel free to start a discussion!

If you wish to contribute to the library, thanks! First, please read the contributing guide. Then you can pick up any issue and fix/implement it!

License

MIT © Nathan Friedly, Vedant K