🎯 背景:为什么要使用消息队列?
您的业务中是否也有处理时间长达 30 分钟甚至更久的"生成报告"、"大数据分析"或"文件导出"等耗时任务?
❌ 传统同步处理的弊端:
- 糟糕的用户体验: 用户请求长时间阻塞,浏览器可能超时,导致用户必须干等着或反复尝试。
- 服务器资源浪费: Web 服务器(如 Node.js Event Loop)被长时间占用,无法响应其他用户的快速请求。
- 请求失败风险高: 任何网络波动或服务器重启都可能导致任务中断,功亏一篑。
✅ 我们的解决方案: 异步任务处理
- 立即响应: 收到请求后,后端应立即返回"任务已创建,请稍后查询结果"。
- 异步解耦: 将耗时任务从主请求线程剥离,交给后台队列(BullMQ Worker)默默处理。
- 最终通知/更新: 任务执行完成后,Worker 自动修改数据库状态,完成整个流程。
💡 技术栈:
- 后端框架:NestJS(TypeScript优先的企业级Node.js框架)
- 消息队列:BullMQ(基于Redis的高性能消息队列,专为Node.js优化)
- 依赖工具:ioredis(Redis客户端,用于BullMQ的连接支撑)
BullMQ 的优势: 基于高性能
ioredis实现,拥有自动重试、失败处理、Job 状态跟踪等生产级功能,并且与 NestJS 的集成优雅方便。
官网BullMQ链接:nest.nodejs.cn/techniques/...
先看下整体结构和结果
文件结构

结果

🛠️ 1. 项目初始化:安装 BullMQ 与配置 Redis
BullMQ 依赖于 Redis 来存储队列信息和任务状态。
1.1 安装所需依赖
bash
pnpm install @nestjs/bullmq bullmq ioredis
1.2 配置 Redis 连接信息
在你的 .env 文件中配置 Redis 地址。这是 BullMQ Worker 和 Queue 共享的连接信息。
代码段
ini
# redis 配置
REDIS_HOST=localhost
REDIS_PORT=6379
🏭 2. 核心模块:创建 BullMQ 队列与处理器
创建一个专用于消息队列的模块 QueueModule。
2.1 配置 QueueModule:连接 Redis 和注册队列
配置 NestJS 的 BullModule.forRootAsync 来连接 Redis,并使用 BullModule.registerQueue 注册一个名为 shortUrl_queue 的队列。
queue.module.ts
js
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { BullModule } from '@nestjs/bullmq';
import { ShortUrlProcessor } from './short-url.processor';
import { QueueService } from './queue.service';
@Module({
imports: [
// 1. 全局配置 BullMQ:连接到 Redis
BullModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
connection: {
host: configService.get<string>('REDIS_HOST'),
port: configService.get<number>('REDIS_PORT'),
},
}),
inject: [ConfigService],
}),
// 2. 注册具体的队列:命名为 'shortUrl_queue'
BullModule.registerQueue({
name: 'shortUrl_queue',
})
],
providers: [
QueueService,
ShortUrlProcessor, // 注册处理器 Worker
],
// ⚠️ 必须导出 BullModule,才能在其他业务模块中使用 @InjectQueue
exports: [BullModule]
})
export class QueueModule { }
2.2 Processor:编写真正的耗时任务逻辑
处理器(Worker)是真正执行任务的地方。它会监听特定队列,并执行 process 方法。
short-url.processor.ts
js
import { OnWorkerEvent, Processor, WorkerHost } from "@nestjs/bullmq";
import { Job } from "bullmq"; // 引入 Job 类型
// @Processor('队列名称'): 告知 NestJS 此 Worker 监听哪个队列
@Processor('shortUrl_queue')
export class ShortUrlProcessor extends WorkerHost {
constructor() {
super();
}
// 任务处理逻辑:这里可以包含数据库操作、文件处理等耗时任务
async process(job: Job) {
console.log(`==== 任务 ID: ${job.id},开始执行消息队列 ====`);
try {
const data = job.data;
console.log('任务接收到的数据: ', data);
// ----------------------------------------------------
// 核心逻辑:这里模拟 30 分钟甚至更长的耗时任务
// ----------------------------------------------------
await new Promise(resolve => setTimeout(resolve, 3 * 1000)); // 实际应为 30 分钟以上的业务逻辑
// TODO: 任务完成后,在这里修改数据库中任务的状态(例如:已完成,并更新结果链接)
console.log('==== 消息队列执行完毕,修改数据库状态 ====');
// process 必须返回结果,否则 BullMQ 会认为任务失败
return { status: 'success', message: '任务成功完成' };
} catch (error) {
console.log('==== 消息队列执行失败 ====', error);
// 抛出错误或返回失败信息,BullMQ 会根据配置进行重试
throw new Error('任务执行过程中出错');
}
}
// 可选:监听任务完成事件 (可用于日志记录)
@OnWorkerEvent('completed')
onCompleted(job: Job, result: any) {
console.log(`[成功] 任务 ${job.id} 完成。`);
}
// 可选:监听任务失败事件 (可用于发送告警通知)
@OnWorkerEvent('failed')
onFailed(job: Job, err: any) {
console.log(`[失败] 任务 ${job.id} 失败,原因: ${err.message}`);
}
}
🚀 3. 业务集成:将耗时任务推送到队列
现在,我们在业务 Controller 中使用 BullModule 提供的工具将任务推送到队列。
3.1 导入 QueueModule
在需要使用队列的业务模块中导入 QueueModule。
js
import { QueueModule } from '../queue/queue.module';
@Module({
imports: [
QueueModule, // 导入我们创建的队列模块
],
})
export class HongguoModule { }
3.2 在 Controller 中注入并推送任务
在 Controller 中,我们使用 @InjectQueue('队列名称') 注入 BullMQ 的 Queue 实例,然后调用 queue.add() 方法。
hongguo.controller.ts
js
import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';
// ... 其他 NestJS 装饰器
@Controller('hongguo')
export class HongguoController {
constructor(
private readonly hongguoService: HongguoService,
// 注入我们注册的 'shortUrl_queue' 队列实例
@InjectQueue('shortUrl_queue')
private taskQueue: Queue,
) { }
@ApiOperation({ summary: '长耗时任务 API' })
@Post('task')
async createTask(@Body() params: any) {
// 1. 业务逻辑:先在数据库创建一条任务记录,状态为"待处理"
const taskRes = { id: 111, status: 'PENDING' }
// 2. 将任务推送到消息队列
await this.taskQueue.add(
'shortUrl-process', // 队列中任务的唯一名称 (Job name)
{ taskId: taskRes.id, inputParams: params }, // 传递给处理器的实际数据
{
removeOnComplete: true, // 任务成功完成后自动清除 Job 记录
removeOnFail: false, // 任务失败后保留,方便调试和手动重试
attempts: 3, // 失败后最多重试 3 次
}
);
// 3. 立即返回响应给前端
return {
data: taskRes,
msg: '✅ 任务已创建,后台正在异步处理中,请稍后查询结果'
};
}
}
💡 效果与结果展示
| 场景 | 同步处理 | 异步处理(BullMQ) |
|---|---|---|
| API 响应时间 | > 30 分钟(或超时) | < 100ms,立即返回 |
| 服务器负载 | Event Loop 阻塞,吞吐量骤降 | 主线程无阻塞,可继续处理其他请求 |
| 任务可靠性 | 中断即丢失 | 支持重试、持久化、失败告警 |
| 用户体验 | 卡顿、焦虑、反复刷新 | 流畅、可轮询状态、体验友好 |
🚀 总结与进阶
通过 NestJS + BullMQ ,我们成功将长耗时任务从主线程剥离,实现了高可用、可扩展、易维护的异步架构。
进阶思考:
-
Job 状态查询: 如何让用户实时查询任务进度?
- 可以通过 BullMQ 的
taskQueue.getJob(jobId)方法结合 WebSocket 实现实时进度推送。
- 可以通过 BullMQ 的
-
可视化监控: 生产环境需要一个面板来监控队列健康和失败任务。
- 可以集成 [Bull Board] 等工具,提供友好的 Web UI 来管理和查看 BullMQ 队列状态。