agenda、bee-queue、bull、bullmq、kue 和 node-resque 都是 Node.js 生态中用于处理异步任务队列的流行库。它们允许开发者将耗时操作(如发送邮件、处理文件、调用外部 API)从主请求流程中解耦,放入后台队列异步执行,从而提升 Web 应用的响应速度和可靠性。这些库通常依赖 Redis 或 MongoDB 作为底层存储,提供任务调度、失败重试、延迟执行、并发控制等核心功能,是构建高可用后端服务的关键组件。
在构建现代 Web 应用时,我们常常需要将耗时操作(如发送邮件、生成报表、处理上传文件)从主请求流程中剥离出来,交给后台任务队列异步处理。Node.js 生态中有多个成熟的任务队列库,它们都基于 Redis 或 MongoDB 等存储后端,但在 API 设计、功能特性、维护状态和适用场景上存在显著差异。本文将从实际工程角度出发,深入比较 Agenda、Bee-Queue、Bull、BullMQ、Kue 和 node-resque,帮助你做出明智的技术选型。
在深入技术细节前,必须明确一点:Kue 已被官方标记为废弃(deprecated)。其 GitHub 仓库 README 明确写道:“Kue is no longer maintained. We recommend using Bull instead.” 因此,任何新项目都不应再选用 Kue。同样,虽然 node-resque 仍在更新,但其社区活跃度和文档完整性远不如 Bull 系列,需谨慎评估。
任务队列库的底层存储直接影响部署架构和运维复杂度。
// Agenda: 使用 MongoDB
const agenda = new Agenda({ db: { address: 'mongodb://127.0.0.1:27017/agenda' } });
agenda.define('send email', async (job) => {
await sendEmail(job.attrs.data.to);
});
// Bull: 使用 Redis
const queue = new Queue('send email', { redis: { port: 6379, host: '127.0.0.1' } });
queue.process('send email', async (job) => {
await sendEmail(job.data.to);
});
// BullMQ: 使用 Redis(新版 API)
const worker = new Worker('send email', async (job) => {
await sendEmail(job.data.to);
}, { connection: { host: '127.0.0.1', port: 6379 } });
// Bee-Queue: 使用 Redis
const queue = new Queue('send email', { redis: { host: '127.0.0.1', port: 6379 } });
queue.process(async (job) => {
await sendEmail(job.data.to);
});
// node-resque: 使用 Redis
const queue = new Queue({ connection: { host: '127.0.0.1', port: 6379 } });
queue.enqueue('send email', 'sendEmail', [to]);
💡 建议:除非已有 MongoDB 技术栈,否则优先选择 Redis 方案 —— 它更轻量、更快,且专为这类场景优化。
不同库对“如何定义和处理任务”有截然不同的抽象方式。
Agenda 将任务视为可调度的对象,支持复杂的重复计划(类似 cron)。
// Agenda: 定义任务并立即或定时执行
agenda.define('delete old users', async (job) => {
await User.remove({ lastLoginAt: { $lt: twoDaysAgo } });
});
// 每天凌晨 2 点运行
await agenda.every('2 hours', 'delete old users');
// 或立即运行一次
await agenda.now('delete old users');
Bull 系列强调“队列”作为核心实体,任务(Job)通过名称提交,由独立的处理器消费。
// Bull: 提交任务
const job = await queue.add('send email', { to: 'user@example.com' });
// BullMQ: 更清晰的分离
const job = await queue.add('send email', { to: 'user@example.com' });
// 处理器在另一处定义(可能在不同进程)
const worker = new Worker('send email', async (job) => { /* ... */ });
Bee-Queue 采用更函数式的风格,任务处理逻辑直接绑定到队列实例。
// Bee-Queue: 定义处理逻辑并提交任务
const emailQueue = new Queue('email');
emailQueue.process(async (job) => {
return sendEmail(job.data.to);
});
// 提交任务
const job = await emailQueue.createJob({ to: 'user@example.com' }).save();
node-resque 的 API 较为底层,需手动管理队列、任务注册和工作进程。
// node-resque: 注册任务函数
const jobs = {
sendEmail: {
perform: async (to) => await sendEmail(to)
}
};
// 提交任务
await queue.enqueue('default', 'sendEmail', ['user@example.com']);
// 启动工作进程
const worker = new Worker({ connection: {}, queues: ['default'] }, jobs);
worker.start();
💡 建议:BullMQ 的 API 最符合现代工程实践 —— 清晰分离生产者与消费者,类型安全更好,适合大型项目。
可靠的任务队列必须处理失败重试和延迟执行。
// Agenda: 内置延迟支持
await agenda.schedule('in 10 minutes', 'send email', { to: 'user@example.com' });
// Bull: 通过 delay 选项
await queue.add('send email', { to: 'user@example.com' }, { delay: 10 * 60 * 1000 });
// BullMQ: 同 Bull
await queue.add('send email', { to: 'user@example.com' }, { delay: 600000 });
// Bee-Queue: 通过 delay() 方法
await emailQueue.createJob({ to: 'user@example.com' }).delay(600000).save();
// node-resque: 不直接支持延迟任务,需自行实现
// Bull: 内置重试,支持 backoff
await queue.add('send email', data, {
attempts: 3,
backoff: { type: 'exponential', delay: 1000 }
});
// BullMQ: 更强大的重试配置
await queue.add('send email', data, {
attempts: 5,
backoff: { type: 'custom', delay: (attemptsMade) => Math.pow(2, attemptsMade) * 1000 }
});
// Bee-Queue: 通过 retries 选项
await emailQueue.createJob(data).retries(3).save();
// Agenda: 通过 fail event 手动重试
agenda.on('fail', async (err, job) => {
if (job.attrs.failures < 3) {
await job.repeatEvery('5 minutes').save();
}
});
💡 建议:BullMQ 提供最灵活的重试和退避策略,适合对可靠性要求高的场景。
生产环境必须能监控队列状态、任务积压和失败率。
bull-board 或 @bull-board/ui),可实时查看队列、任务状态、重试情况。// BullMQ + bull-board 示例
import { createBullBoard } from '@bull-board/api';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
import { ExpressAdapter } from '@bull-board/express';
const serverAdapter = new ExpressAdapter();
createBullBoard({
queues: [new BullMQAdapter(myQueue)],
serverAdapter
});
app.use('/admin/queues', serverAdapter.getRouter());
💡 建议:若需开箱即用的监控能力,BullMQ 是最佳选择。
| 场景 | 推荐库 | 理由 |
|---|---|---|
| 全新项目,追求现代化、类型安全、强大功能 | BullMQ | 完整的 TypeScript 支持、灵活的重试/延迟、优秀监控生态 |
| 已有 MongoDB 技术栈,不想引入 Redis | Agenda | 唯一成熟的 MongoDB 任务队列方案 |
| 轻量级需求,简单任务处理,小团队快速迭代 | Bee-Queue | API 简洁,代码量少,易于理解 |
| 从 Ruby Resque 迁移,或熟悉其模型 | node-resque | 概念一致,但需接受较弱的文档和生态 |
| 任何新项目 | 避免 Kue | 官方已废弃,存在未修复 bug 和安全风险 |
最终,任务队列库的选择应基于你的技术栈现状、团队熟悉度和长期维护成本。对于绝大多数新项目,BullMQ 是最平衡、最面向未来的选择。
选择 bullmq 作为新项目的首选。它是 Bull 的现代化重写,采用 TypeScript 开发,提供更强的类型安全、更灵活的重试/延迟配置、更低的内存占用和更好的监控集成。API 设计清晰,分离了生产者与消费者,适合中大型应用的长期维护。
选择 bull 如果你正在维护一个已使用它的老项目,或者需要一个成熟稳定的 Redis 队列方案。它拥有丰富的功能和活跃的社区插件(如 bull-board)。但新项目应优先考虑其继任者 BullMQ,因为 Bull 存在一些已知的设计缺陷和内存问题。
选择 agenda 如果你的项目已经重度使用 MongoDB 且希望避免引入 Redis 依赖。它提供了类似 cron 的复杂调度能力,适合需要基于时间触发的周期性任务场景。但需注意其 API 较为老旧,且缺乏官方 Web UI 监控工具。
选择 bee-queue 如果你需要一个轻量、简洁、无额外依赖(仅需 Redis)的任务队列方案。它的 API 设计直观,适合小型项目或对 bundle size 敏感的场景。但功能相对基础,缺少高级重试策略和内置监控支持。
不要选择 kue 用于任何新项目。它已被官方明确标记为废弃(deprecated),不再接受功能更新或安全修复。现有项目应制定迁移计划,转向 BullMQ 或其他活跃维护的替代方案。
选择 node-resque 仅当你有 Ruby Resque 的使用经验,或需要与现有 Resque 生态集成。它忠实复刻了 Resque 的概念模型,但文档和社区支持较弱,学习曲线较陡。对于大多数新项目,BullMQ 是更高效的选择。
The fastest, most reliable, Redis-based distributed queue for Node.
Carefully written for rock solid stability and atomicity.
Follow @manast for *important* Bull/BullMQ/BullMQ-Pro news and updates!
You can find tutorials and news in this blog: https://blog.taskforce.sh/
Do you need to work with BullMQ on platforms other than Node.js? If so, check out the BullMQ Proxy
Supercharge your queues with a professional front end:
Sign up at Taskforce.sh
|
| Dragonfly is a new Redis™ drop-in replacement that is fully compatible with BullMQ and brings some important advantages over Redis™ such as massive better performance by utilizing all CPU cores available and faster and more memory efficient data structures. Read more here on how to use it with BullMQ. |
Some notable organizations using BullMQ:
|
|
|
|
|
|
|
|
|
Install:
$ yarn add bullmq
Add jobs to the queue:
import { Queue } from 'bullmq';
const queue = new Queue('Paint');
queue.add('cars', { color: 'blue' });
Process the jobs in your workers:
import { Worker } from 'bullmq';
const worker = new Worker('Paint', async job => {
if (job.name === 'cars') {
await paintCar(job.data.color);
}
});
Listen to jobs for completion:
import { QueueEvents } from 'bullmq';
const queueEvents = new QueueEvents('Paint');
queueEvents.on('completed', ({ jobId }) => {
console.log('done painting');
});
queueEvents.on(
'failed',
({ jobId, failedReason }: { jobId: string; failedReason: string }) => {
console.error('error painting', failedReason);
},
);
Adds jobs with parent-child relationship:
import { FlowProducer } from 'bullmq';
const flow = new FlowProducer();
const originalTree = await flow.add({
name: 'root-job',
queueName: 'topQueueName',
data: {},
children: [
{
name: 'child-job',
data: { idx: 0, foo: 'bar' },
queueName: 'childrenQueueName',
children: [
{
name: 'grandchild-job',
data: { idx: 1, foo: 'bah' },
queueName: 'grandChildrenQueueName',
},
{
name: 'grandchild-job',
data: { idx: 2, foo: 'baz' },
queueName: 'grandChildrenQueueName',
},
],
},
{
name: 'child-job',
data: { idx: 3, foo: 'foo' },
queueName: 'childrenQueueName',
},
],
});
This is just scratching the surface, check all the features and more in the official documentation
Since there are a few job queue solutions, here is a table comparing them:
| Feature | BullMQ-Pro | BullMQ | Bull | Kue | Bee | Agenda |
|---|---|---|---|---|---|---|
| Backend | redis | redis | redis | redis | redis | mongo |
| Observables | ✓ | |||||
| Group Rate Limit | ✓ | |||||
| Group Support | ✓ | |||||
| Batches Support | ✓ | |||||
| Parent/Child Dependencies | ✓ | ✓ | ||||
| Deduplication (Debouncing) | ✓ | ✓ | ✓ | |||
| Deduplication (Throttling) | ✓ | ✓ | ✓ | |||
| Priorities | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Concurrency | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Delayed jobs | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Global events | ✓ | ✓ | ✓ | ✓ | ||
| Rate Limiter | ✓ | ✓ | ✓ | |||
| Pause/Resume | ✓ | ✓ | ✓ | ✓ | ||
| Sandboxed worker | ✓ | ✓ | ✓ | |||
| Repeatable jobs | ✓ | ✓ | ✓ | ✓ | ||
| Atomic ops | ✓ | ✓ | ✓ | ✓ | ||
| Persistence | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| UI | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Optimized for | Jobs / Messages | Jobs / Messages | Jobs / Messages | Jobs | Messages | Jobs |
Fork the repo, make some changes, submit a pull-request! Here is the contributing doc that has more details.
Thanks for all the contributors that made this library possible, also a special mention to Leon van Kammen that kindly donated his npm bullmq repo.