开发讨论系统架构时,经常会提到 MQ、Worker、异步任务、削峰填谷。
听起来概念很多,其实可以从一个问题开始理解:如果用户提交了一个耗时任务,后端是否必须等它做完,才能继续处理其他事情?
一、MQ 到底是什么?
MQ 是 Message Queue(消息队列) 的缩写:
- Message(消息):告诉另一个程序发生了什么,或者需要做什么。
- Queue(队列):让消息按约定的规则等待、分发与处理。
可以把 MQ 理解成餐厅里的订单系统。
没有订单系统时,服务员接单后要一直跟着厨师,等菜做好才能回去接待下一位顾客。有了订单系统,服务员只需把订单提交进去,就可以继续接待顾客;厨师则从订单系统中领取订单并做菜。
软件里也是类似的分工:
| 角色 | 做什么 | 餐厅类比 |
|---|---|---|
| Producer(生产者) | 发送消息 | 服务员下单 |
| MQ(消息队列) | 接收、存储或转发消息 | 订单系统 |
| Consumer(消费者) | 读取消息并处理 | 厨师 |
MQ 本身通常不负责完成业务任务,它负责在不同程序之间传递消息。 真正执行任务的是消费者,例如后台的 Worker(工作进程)。
二、为什么 AI 产品需要 MQ?
以用户在 Lingbot 上传一份 300 页 PDF、调用 MinerU 解析为例。
最直接的流程是:
用户上传 PDF
↓
后端调用 MinerU
↓
等待解析完成
↓
返回最终结果如果解析需要 60 秒,用户可能得等 60 秒才能获得最终响应。请求还可能因网络或网关超时而失败。
引入 MQ 后,可以变成:
用户上传 PDF
↓
后端保存文件、创建任务
↓
发送“解析 PDF”消息到 MQ
↓
立即返回“任务已提交”
↓
MQ 队列
↓
Worker 获取消息
↓
调用 MinerU 解析
↓
保存结果、更新状态
↓
用户查看解析结果这里有一个重要区别:上传请求结束,不代表解析任务已经完成。 前端需要用任务状态、进度提示和结果通知,让用户知道后台正在处理。
需要注意,异步处理不一定需要 MQ。后端也能通过其他后台任务机制实现异步;MQ 是当任务较多、服务需要协作时的一种常见方案。
1. 异步处理:接单和干活分开
后端接收任务后,不必占着原请求一直等处理结束。耗时工作交给后台 Worker。
这对 PDF 解析、知识库构建、AI PPT 生成、报告导出等功能都很有用。
2. 削峰填谷:高峰期先排队
假设平时每分钟只有 10 个解析任务,突然有 500 个用户同时上传文件。
如果全部同时执行,解析服务可能因 CPU、内存或模型调用限制而过载。MQ 可以先接收待处理任务,Worker 再按容量逐步消费:
500 个解析任务
↓
MQ 排队
↓
┌─────┼─────┐
↓ ↓ ↓
Worker Worker Worker
1 2 3这就是削峰填谷。但 MQ 不会凭空提升解析能力。如果任务持续进入得比处理得快,队列仍会积压,用户等待时间会变长。
3. 服务解耦:各做各的事
如果后端 API 直接调用解析服务,解析服务不可用时,请求往往立即失败。
引入可靠的消息队列后,后端可以先提交任务,解析服务恢复后再继续处理积压消息。前提是队列本身可用,并配置了相应的持久化、确认和重试机制。
此外,解析压力变大时,可以独立增加 Worker 数量,而不必连 API 服务一起扩容。
三、MQ、Redis、MySQL 到底怎么分工?
这三个组件常在同一个系统出现,但典型职责不同。
| 组件 | 主要作用 | PDF 解析场景 |
|---|---|---|
| MySQL | 可靠保存、管理业务数据 | 保存任务记录、处理状态、结果地址 |
| Redis | 快速读取数据、管理临时状态 | 缓存任务进度,如 60% |
| MQ | 在程序之间传递消息 | 发送“请解析任务 1001”的消息 |
| Worker | 真正执行后台任务 | 调用 MinerU 并保存结果 |
例如,MQ 中的一条消息可能是:
{
"task_id": "1001",
"action": "parse_pdf"
}它的意思不是“PDF 已经解析完成”,而是“有一个 PDF 解析任务需要处理”。
Redis 中则可能保存:
task:1001:progress = 60%MySQL 中可以保存:
task_id: 1001
file_name: 产品说明书.pdf
status: processing
created_at: 10:00另外,原始 PDF 和解析后的文件通常由对象存储等文件存储服务负责保存,而不是直接塞进 MQ。消息中一般只需要传递任务 ID 或文件地址。
这里的分工是常见模式,并非硬性规定:Redis Streams 也能承担消息队列职责,任务状态也不一定非要缓存到 Redis。
四、消息被 Worker 取走后,就算处理完成了吗?
不一定。
想象一下:Worker 从 MQ 领取了任务,刚处理到一半就崩溃了。
如果 MQ 只要把消息发出去就立即认定完成,那么这项任务可能丢失。
因此,很多消息队列支持 ACK(Acknowledgement,消息确认):
MQ 投递任务
↓
Worker 处理
↓
处理成功
↓
Worker 发送 ACK
↓
MQ 确认消息已处理如果 Worker 没有确认,MQ 在相应配置下可能重新投递消息。这里也带来一个问题:同一任务可能被执行多次。
所以开发经常把以下两个概念一起讨论:
- 重试(Retry):失败后再尝试执行。
- 幂等(Idempotency):同一业务请求重复执行,不应产生不合理的重复结果。
例如 PDF 任务执行前,Worker 先根据 task_id 检查任务是否已经完成,避免重复解析、重复写入。实际系统还要考虑并发竞争、异常恢复以及最终状态的一致性。
五、一个完整的 AI 文档解析架构
把前面的组件放在一起:
用户上传 PDF
↓
后端 API
│
┌──────────┼─────────┐
↓ ↓ ↓
对象存储 MySQL MQ
保存文件 保存任务 传递消息
↓
Worker
↓
调用 MinerU
↓
保存结果、更新状态
↓
前端获取进度最后还有一个容易混淆的地方:MQ 负责后端程序之间的消息传递,不等于负责把进度推送给用户。
前端展示“正在解析,60%”时,可以采用轮询、SSE 或 WebSocket 等方式获取最新状态。它们解决的是前端怎么知道进度,而 MQ 解决的是后台任务怎么交接。
最后记住三句话
异步处理: 先接收任务,不必在原请求中一直等待最终结果。
削峰填谷: 请求多时先排队,按后台处理能力逐步执行。
服务解耦: 接收任务与执行任务的服务可以独立运行、独立扩容。
对于 AI 产品经理,不必一开始就研究 RabbitMQ、Kafka、RocketMQ 的底层差异。先搞清楚 MQ、Worker、MySQL、Redis 在业务流程中的分工,已经能理解很多 AI 产品的后端设计。