为什么需要 BullMQ
问题:HTTP 请求不适合承担所有工作
假设用户请求生成一份大型报表:
HTTP 请求 -> 查询大量数据 -> 生成文件 -> 上传文件 -> 发送邮件 -> HTTP 响应
如果全部在请求中同步完成,会出现:
- 请求超时。
- 用户必须长时间等待。
- 突发流量直接压垮数据库或第三方服务。
- Node.js 进程重启后,执行中的工作难以恢复。
- Web 进程既处理请求又处理重任务,资源相互争抢。
使用 BullMQ 后:
HTTP 请求
|
v
Producer --添加任务--> Redis/BullMQ Queue
| |
v v
返回 202 Accepted Worker 获取并执行
|
v
completed / failed
Web 服务只负责验证请求和提交任务,Worker 独立处理耗时工作。
BullMQ 解决了什么
削平流量高峰
一分钟到来 10,000 个任务,不代表必须在一分钟内同时执行 10,000 个。队列可以积压任务,Worker 按可承受的并发速度消费。
这叫作负载整形或削峰:用更长的处理时间换取系统稳定性。
分离提交和执行
Producer 与 Worker 可以运行在:
- 同一个 Node.js 进程。
- 同一台机器的不同进程。
- 不同服务器或容器。
生产环境通常将 Web 服务和 Worker 分开部署,避免重任务拖慢 HTTP 接口。
自动重试和延迟
第三方 API 暂时失败时,可以采用退避策略重试,而不是让用户重新提交整个请求。
水平扩展
增加 Worker 实例即可并行消费同一队列。官方文档强调 BullMQ 可以通过增加 Worker 横向扩展。
保存任务状态
任务会经历等待、执行、完成或失败等状态,便于查询进度和排查错误。
Job 的生命周期
根据官方 Architecture,一个普通任务可能经历:
wait / prioritized / delayed
|
v
active
/ \
v v
completed failed
|
+-- retry --> wait
Flow 中存在父子依赖时,父任务还可能进入 waiting-children,等待子任务完成。
理解生命周期很重要,因为“任务已经添加”并不等于“任务已经完成”。
BullMQ 与 Node-Redis 的区别
Node-Redis 是通用 Redis 客户端:
await client.rPush("jobs", JSON.stringify(data));
BullMQ 是任务队列框架,它在 Redis 之上提供:
- 任务状态和锁。
- Worker 协调。
- 重试与退避。
- 延迟、优先级和定时任务。
- Stalled Job 恢复。
- 事件、指标和任务依赖。
自己用 LPUSH / BLPOP 实现的只是简单队列,还需要自行解决 Worker 崩溃、重复执行、失败记录、重试和清理等问题。
BullMQ 与消息中间件的区别
BullMQ 很适合 Node.js 应用中的后台任务,但不是所有消息系统的唯一选择。
| 需求 | 常见选择 |
|---|---|
| Node.js 后台任务、延迟和重试 | BullMQ |
| 复杂跨系统路由、协议互操作 | RabbitMQ |
| 大规模事件流、长时间保留和回放 | Kafka |
| 云平台深度集成的托管队列 | SQS 等云队列 |
| 单进程、无需持久化的小任务 | 内存队列或 Worker Threads |
选型应依据交付语义、吞吐量、运维能力和语言生态,而不是只比较 API 是否简单。
任务可能重复执行
BullMQ 首页描述的是尽力实现一次交付,但最坏情况下可能至少一次交付。Worker 崩溃、锁丢失或重试时,同一业务任务可能再次运行。
因此:
任务成功 = 处理函数返回成功
业务安全 = 即使处理函数重复运行,最终结果仍正确
例如发送付款请求时,应使用业务幂等键或数据库唯一约束,不能只相信 Job ID。
适合 BullMQ 的场景
- 邮件、短信和推送发送。
- 图片压缩、视频转码。
- 报表和文件生成。
- Webhook 投递和失败重试。
- 数据导入、导出和批处理。
- 调用有速率限制的第三方 API。
- 定时同步任务。
- 多步骤、有依赖关系的后台工作流。
不适合直接使用 BullMQ 的场景
- 用户必须立即拿到结果,任务本身非常快。
- 业务要求严格的全局 Exactly Once,却没有额外幂等机制。
- 团队无法维护 Redis 的持久化、内存和监控。
- 任务数据必须长期作为审计记录保存。
- CPU 密集任务仍和 Web 服务运行在同一事件循环且没有隔离。
最佳实践起点
- 让一个 Job 只承担一个清晰目标。
- Job 数据只保存执行所需的最小信息,常见做法是保存数据库记录 ID。
- Worker 设计成幂等、可重试。
- Web 接口添加任务后返回
202 Accepted和 Job ID。 - 对 Producer 和 Worker 使用不同的 Redis 断线策略。
- 控制并发,不把“多 Worker”误解为“无限并行”。
- 配置完成和失败任务的保留数量,避免 Redis 无限增长。
- Redis 使用
noeviction,队列键不能像缓存键一样被随意淘汰。
本章小结
BullMQ 的核心价值是把任务执行从请求路径中解耦,用 Redis 保存任务状态,并通过 Worker 控制并发、重试和扩展。引入队列并不会自动解决一致性问题;安全的任务仍需要幂等设计、资源限制和故障策略。