消息队列
2026/3/25大约 4 分钟
消息队列
消息队列(MQ)的核心价值就三个字:解耦、异步、削峰。但引入 MQ 也引入了复杂性——消息丢失、消息重复、消息顺序、消息堆积。这篇文章帮你理清 MQ 的核心问题和选型。
基础入门:消息队列是什么?
一句话理解 MQ
MQ 就是一个"消息中转站":
生产者把消息放进去 → 消费者从里面取出来
就像快递柜:
你把包裹放进去(生产)→ 快递员取出来(消费)
你不需要知道快递员是谁,快递员也不需要认识你 → 解耦三大核心价值
| 价值 | 说明 | 场景 |
|---|---|---|
| 解耦 | 生产者和消费者不直接调用 | 订单服务发 MQ → 通知/积分/日志服务各自消费 |
| 异步 | 非核心操作异步化 | 下单后发 MQ → 异步发邮件、加积分 |
| 削峰 | 缓冲突发流量 | 秒杀请求入 MQ → 消费者按能力处理 |
MQ 解决什么问题?
1. 解耦:生产者和消费者不需要直接调用
订单服务 → MQ → 通知服务、积分服务、日志服务
新增消费者?加个订阅就行,不用改订单服务
2. 异步:非核心操作异步化
用户下单 → 发 MQ → 立即返回"下单成功"
MQ 消费者 → 扣库存、发通知、加积分(异步执行)
3. 削峰填谷:应对突发流量
秒杀时 10000 QPS → MQ 缓冲 → 消费者按能力消费(如 1000 QPS)
数据库不会被瞬间压垮三大经典问题
消息丢失
生产者 → MQ → 消费者,三个环节都可能丢
1. 生产者发消息失败
→ RocketMQ:同步发送 + 重试
→ Kafka:acks=all(所有 ISR 副本确认)
2. MQ 自身丢失
→ 持久化:刷盘策略(同步刷盘 vs 异步刷盘)
→ 副本:至少 2 个副本
3. 消费者处理失败
→ 手动 ACK:处理成功才确认
→ 重试机制:失败后重试(注意重试次数和间隔)消息重复(幂等性)
消费者收到了重复消息 → 业务操作必须是幂等的
幂等方案:
- 数据库唯一约束(INSERT ... ON DUPLICATE KEY UPDATE)
- Redis SETNX(分布式锁)
- 全局 ID + 状态判断(先查状态,已处理就跳过)
- 数据库乐观锁(版本号)
// RocketMQ 消费者幂等示例
@RocketMQMessageListener(topic = "order", consumerGroup = "order-group")
public class OrderConsumer implements RocketMQListener<OrderMessage> {
@Override
public void onMessage(OrderMessage msg) {
// 用消息 ID 做幂等判断
if (orderService.existsByMsgId(msg.getMsgId())) {
return; // 已处理过,跳过
}
orderService.process(msg);
}
}消息顺序
RocketMQ:同一个订单的消息发到同一个 Queue → 单消费者顺序消费
Kafka:同一个 key 的消息发到同一个 Partition → 单消费者顺序消费
注意:全局顺序 = 单 Partition 单 Consumer → 吞吐量极低
实际做法:只保证局部顺序(同一订单/用户的消息有序)选型对比
| 特性 | RocketMQ | Kafka | RabbitMQ |
|---|---|---|---|
| 语言 | Java | Scala | Erlang |
| 吞吐量 | 10万级 | 100万级 | 万级 |
| 延迟 | ms级 | ms级 | μs级 |
| 消息可靠性 | 高(支持事务消息) | 高(acks配置) | 高 |
| 顺序消息 | 支持 | 分区内有序 | 不保证 |
| 延时消息 | 支持 | 不支持 | 不支持(插件) |
| 适用场景 | 业务消息(订单、支付) | 日志、大数据 | 复杂路由 |
选型建议
电商/金融业务消息 → RocketMQ(功能完善,支持事务消息)。日志采集/大数据 → Kafka(吞吐量最高)。小项目/复杂路由 → RabbitMQ。大多数 Java 后端项目选 RocketMQ 就对了。
面试高频题
Q1:如何保证消息不丢失?
三端保证:生产端用同步发送 + 重试,MQ 端用持久化 + 多副本,消费端用手动 ACK + 重试。RocketMQ 的事务消息可以进一步保证生产端和 MQ 端的一致性。
Q2:消息堆积怎么处理?
先定位堆积原因(消费者太慢?生产者突发?)。应急:临时增加消费者实例。根治:优化消费逻辑、增加消费并行度、检查是否有慢 SQL。

