MQ 从 0 到 1(一):为什么需要消息队列
MQ,全称 Message Queue,也就是消息队列。
它不是某一种具体技术,而是一类中间件思想:
生产者把消息发到队列里,消费者从队列里取消息处理。
听起来很简单,但它解决的是后端系统里非常常见的一组问题:
- 一个请求里要做的事情太多,接口响应越来越慢;
- 一个业务动作会牵扯多个系统,代码耦合越来越重;
- 高峰期流量突然打进来,数据库和下游服务顶不住;
- 跨系统操作不可能都放在同一个事务里,需要最终一致性;
- 某些任务失败后需要重试,而不是直接丢掉。
这篇先不急着写 RabbitMQ 代码,先把 MQ 出现的原因讲清楚。
不用 MQ 的同步调用
假设一个电商系统里,用户下单成功之后要做这些事:
1. 创建订单2. 扣减库存3. 生成支付单4. 发短信通知5. 发站内信6. 增加用户积分7. 推送数据到搜索系统8. 记录运营分析数据如果所有事情都写在一个接口里,大概会变成这样:
public void createOrder(CreateOrderCommand command) { Order order = orderService.create(command); stockService.deduct(order); paymentService.createPayment(order); smsService.sendOrderCreatedMessage(order); messageService.sendSiteMessage(order); pointService.addPoint(order); searchService.syncOrder(order); analysisService.record(order);}代码一眼能看懂,但问题也很明显。
问题一:接口响应慢
用户真正关心的是:
我这个订单有没有创建成功?
但同步调用会让用户等所有步骤都完成。
假设每个步骤耗时如下:
| 操作 | 耗时 |
|---|---|
| 创建订单 | 50ms |
| 扣减库存 | 40ms |
| 生成支付单 | 30ms |
| 发短信 | 300ms |
| 发站内信 | 80ms |
| 增加积分 | 50ms |
| 同步搜索系统 | 200ms |
| 记录分析数据 | 60ms |
总耗时可能接近 800ms。
如果短信服务偶尔慢一点,接口就会更慢。用户看到的就是一直转圈。
但短信、积分、搜索同步这些事情,并不一定要阻塞下单接口。
这时候就可以改成:
下单接口只做核心链路:创建订单 -> 扣库存 -> 生成支付单
其他事情发消息:发送 OrderCreatedEvent消费者异步处理:
短信消费者:发送短信积分消费者:增加积分搜索消费者:同步搜索数据分析消费者:记录运营数据接口响应会变快,因为非核心动作被移到了异步链路。
问题二:系统耦合太重
同步调用还有一个问题:订单服务知道得太多。
订单服务本来只应该关心订单,结果它还要知道:
- 短信服务怎么调用;
- 积分服务怎么调用;
- 搜索服务怎么调用;
- 运营分析服务怎么调用;
- 新增一个优惠券服务时,还要改订单服务。
这会让订单服务越来越胖。
用了 MQ 之后,订单服务只需要做一件事:
订单创建成功后,发布一条订单创建消息。至于谁关心这条消息,由消费者自己订阅。
OrderCreatedEvent -> 短信服务消费 -> 积分服务消费 -> 搜索服务消费 -> 分析服务消费 -> 优惠券服务消费订单服务不需要知道后面有多少消费者。
这就是解耦。
问题三:高峰流量打垮下游
同步调用是“来一个请求,立刻处理一个请求”。
如果一场秒杀活动瞬间来了 10 万个请求,后端会直接把压力传给数据库和下游服务。
大量请求 -> 订单服务 -> 库存服务 -> 数据库数据库顶不住时,系统就会雪崩。
MQ 可以作为缓冲层。
大量请求 -> 订单服务 -> MQ -> 消费者慢慢处理 -> 数据库上游先把任务放进队列,下游按自己的处理能力慢慢消费。
这叫削峰填谷。
注意,MQ 不是让系统凭空变强,它只是把瞬时压力摊平。真正的处理能力还是取决于消费者数量、数据库能力、业务幂等和限流策略。
问题四:跨系统事务不好处理
本地事务只能保证一个数据库里的操作一致。
比如下单时:
订单库:创建订单库存库:扣库存积分库:加积分搜索系统:同步文档这些操作不在一个数据库里,不能简单用一个 MySQL 事务包起来。
在真实项目里,更常见的做法是:
核心事务先成功。然后发送业务事件。消费者异步处理自己的数据。如果失败,就重试或补偿。这就是最终一致性的思路。
用户下单成功后,积分晚几秒到账,通常可以接受。
但订单创建成功、支付单没生成,这种核心链路就不能随便异步,要谨慎设计。
MQ 适合放什么
适合放进 MQ 的事情,一般有几个特点:
- 不需要立即给用户返回结果;
- 失败后可以重试;
- 能接受短暂延迟;
- 可以设计幂等;
- 适合被多个系统订阅。
比如:
| 场景 | 是否适合 MQ | 原因 |
|---|---|---|
| 下单后发短信 | 适合 | 短暂延迟可接受 |
| 下单后加积分 | 适合 | 可以异步,失败可补偿 |
| 下单后同步 ES | 适合 | 搜索数据可以最终一致 |
| 支付成功后修改订单状态 | 谨慎 | 核心状态,必须保证可靠 |
| 用户登录校验密码 | 不适合 | 必须立即返回结果 |
| 查询商品详情 | 不适合 | 是读请求,不是异步任务 |
MQ 不是万能药,不是所有操作都应该丢到队列里。
MQ 的核心角色
不管是 RabbitMQ、RocketMQ 还是 Kafka,最基本的角色都差不多。
生产者 Producer 发送消息
消息中间件 Broker 保存消息、路由消息、投递消息
消费者 Consumer 接收消息并处理业务用订单场景表示就是:
订单服务 发送 OrderCreatedEvent | v MQ | +--> 短信消费者 +--> 积分消费者 +--> 搜索消费者 +--> 分析消费者生产者只负责把消息发出去。
消费者只负责处理自己关心的消息。
Broker 负责在中间保存和分发消息。
消息一般长什么样
消息本质上就是一份数据。
比如订单创建消息:
{ "eventId": "202607091200000001", "eventType": "ORDER_CREATED", "orderId": 10001, "userId": 90001, "occurredAt": "2026-07-09 12:00:00"}业务里不要只传一个字符串,也不要把整个大对象无脑塞进去。
建议消息里包含:
- 全局唯一的消息 ID;
- 事件类型;
- 业务主键,比如
orderId; - 必要的业务字段;
- 事件发生时间;
- 版本号,方便后面兼容升级。
很多时候,消息里只放 orderId 就够了,消费者再根据 orderId 查询自己需要的数据。
MQ 会带来什么新问题
MQ 解决了一些问题,也会引入新的复杂度。
最常见的是:
- 消息发送失败怎么办;
- 消息发出去了但数据库事务回滚怎么办;
- 消费者处理失败怎么办;
- 同一条消息被消费多次怎么办;
- 消息顺序是否需要保证;
- 消息堆积了怎么办;
- MQ 服务挂了怎么办;
- 消费太慢怎么扩容;
- 如何监控和告警。
所以 MQ 不是“加了就高级”,而是要配合可靠性、幂等、重试、死信队列、监控一起设计。
后面的文章会围绕这些问题展开。
MQ 和 @Async 的区别
前面写过 @Async。
@Async 和 MQ 都能做异步,但定位不一样。
| 对比 | @Async | MQ |
|---|---|---|
| 是否跨服务 | 通常不跨服务 | 天然适合跨服务 |
| 任务是否持久化 | 默认不持久化 | 通常可以持久化 |
| 失败重试 | 需要自己做 | 中间件和业务一起做 |
| 削峰能力 | 弱 | 强 |
| 系统复杂度 | 低 | 更高 |
| 适合场景 | 本服务内部轻量异步 | 跨系统事件、可靠异步、削峰 |
简单说:
本服务内部,轻量异步,可以先考虑 @Async。跨服务、不能丢、要削峰,优先考虑 MQ。小结
这一篇先记住几个点:
- MQ 的核心思想是生产者发消息,消费者异步消费;
- MQ 常用来做异步、解耦、削峰和最终一致性;
- MQ 适合处理可延迟、可重试、可幂等的任务;
- MQ 会引入消息可靠性、重复消费、堆积、监控等新问题;
- 不要为了用 MQ 而用 MQ,要先判断业务是否真的需要。
下一篇开始进入 RabbitMQ,看它是怎么用交换机、队列和绑定关系完成消息路由的。
If this article helped you, please share it with others!
Some information may be outdated






