什么是消息队列
消息队列可以先粗暴理解成一句话:它是 API 和 Worker 之间的缓冲带与传送带。
在长链路任务里,消息队列并不是“高级可选项”,而是最常见、最实用的基础设施之一。
为什么任务系统几乎总会引入队列
假设一个 AI 识别接口突然在 1 分钟内收到 5000 个任务。
如果没有队列,API 服务通常只剩下两种糟糕选择:
- 自己同步做,直接把请求线程打满
- 直接调 Worker 服务,瞬间把 Worker 打爆
而引入队列之后,系统会变成这样:
flowchart LR
A[突发 5000 个请求] --> B[API 快速接收]
B --> C[消息队列缓冲]
C --> D1[Worker 1]
C --> D2[Worker 2]
C --> D3[Worker 3]
D1 --> E[状态存储]
D2 --> E
D3 --> E
这张图表达的就是消息队列最核心的三个作用:
- 削峰:高峰来了先排队,不要把执行器压垮
- 解耦:提交任务和执行任务不是同一个节奏
- 并行:多个 Worker 可以并发消费
消息队列到底存的是什么
初学者容易误会:队列里是不是要塞完整业务数据?
一般不建议。
更常见的做法是:
- 队列里只放轻量消息
- 大对象放数据库或对象存储
- Worker 消费消息后,再根据
taskId去取完整上下文
比如一条消息可以非常简单:
{
"taskId": "task_20260414_001",
"taskType": "contract_parse",
"attempt": 1
}
这样设计的原因是:
- 队列更轻,吞吐更稳
- 消息重投更简单
- 数据变更有统一来源,不容易出现消息和数据库不一致
一个最小案例:批量合同解析
假设用户一次性上传了 1000 个合同 PDF。
没有队列时
API 可能会在一次请求里直接做这些事:
- 保存文件
- 调 OCR 服务
- 做字段提取
- 写数据库
结果通常是:
- 请求时间很长
- OCR 服务被瞬间打满
- 失败后前端根本不知道做到哪一步
有队列时
流程会变成:
- API 为每个文件创建一个任务
- 把
taskId发送到队列 - Worker 按自己的消费能力慢慢拿任务
- 状态统一写回状态存储
于是系统的压力被平滑了,提交和执行的节奏也分开了。
消息队列的关键语义
消息队列不是简单的“列表”,工程上至少要关注下面四件事。
1. 投递
任务进入队列,等待消费者处理。
2. 消费
Worker 从队列中拉取或订阅消息。
3. 确认
任务处理成功后,要告诉队列“这条消息可以删了”。
4. 重投
如果 Worker 崩了、超时了、处理失败了,消息要能重新投递。
可以用一个简单时序图理解:
sequenceDiagram
autonumber
participant API as API
participant MQ as 消息队列
participant W as Worker
API->>MQ: publish(task_id)
W->>MQ: consume(task_id)
alt 处理成功
W->>MQ: ack
else 处理失败 / 崩溃
MQ-->>W: timeout / nack
MQ->>MQ: requeue
end
这也是为什么很多队列天然和“失败重试”绑在一起讲。
为什么不是 API 直接调用 Worker
理论上可以直连,但问题会很多:
| 方案 | 问题 |
|---|---|
| API 直接调 Worker | 耦合太紧,Worker 挂了 API 也受影响 |
| API 写 DB,Worker 轮询 DB | 轮询效率低,数据库压力大 |
| API 发队列消息 | 解耦、缓冲、扩展性最好 |
消息队列最重要的价值,不是“高级”,而是它把不同角色的职责分清楚了。
队列不是状态库,也不是结果库
这是另一个很常见的误区。
队列负责什么
- 把任务送到消费者手里
- 提供消费与重投机制
- 帮系统做流量缓冲
队列不负责什么
- 不负责持久保存任务完整状态
- 不负责给前端展示进度
- 不负责长期保存结果文件
所以真正的工程实践通常是:
- 队列 管流转
- 数据库 / Redis 管状态
- 对象存储 / 数据库 管结果
常见选型怎么理解
| 技术 | 更适合什么场景 |
|---|---|
| Redis List / Stream | 轻量任务队列、实现简单、延迟低 |
| RabbitMQ | 经典业务队列,确认与路由能力强 |
| Kafka | 高吞吐事件流、可回放、适合大规模日志与数据流 |
如果只是做业务任务系统,很多团队一开始用 Redis 或 RabbitMQ 就够了。Kafka 并不是默认更好,而是更偏大规模事件流平台。
初学者最容易踩的坑
1. 把大文件内容直接塞进消息体
结果是队列负担很重,消息重投也变得昂贵。
2. 认为“进了队列就一定只会消费一次”
现实里更常见的是 at-least-once,也就是“至少一次”。
这意味着:
- 同一任务可能被执行多次
- 业务层必须做幂等
3. 只上队列,不做状态存储
这样用户只能“提交成功”,却永远不知道任务跑到哪里了。
这一篇要记住的核心点
- 消息队列的作用是削峰、解耦、异步和并行
- 队列里最好只放轻量消息,完整上下文放状态存储里
- 队列解决的是“怎么把任务送去执行”,不是“怎么展示任务状态”
下一篇继续往下看:当队列有了之后,整套任务执行架构应该怎么搭起来。