消息队列核心问题
理解 MQ 不能停留在“发送和消费消息”,还要把消息丢失、重复消费、顺序消息、消息积压、延迟消息、事务消息和最终一致性串起来。
1. MQ 的作用
常见作用:
- 异步处理。
- 系统解耦。
- 削峰填谷。
- 广播通知。
- 最终一致性。
典型例子:
text
下单成功
-> 发送订单创建消息
-> 库存服务扣减库存
-> 积分服务增加积分
-> 通知服务发送短信2. 消息丢失
消息丢失可能发生在三个阶段:
text
生产者 -> MQ Broker -> 消费者2.1 生产者丢失
解决:
- 发送确认机制。
- 失败重试。
- 本地消息表。
- 事务消息。
2.2 Broker 丢失
解决:
- 消息持久化。
- 主从复制。
- 多副本同步。
- 合理刷盘策略。
2.3 消费者丢失
消费者拉到消息后,如果业务没处理完就提交 offset 或 ack,就可能丢失。
解决:
- 业务处理成功后再 ack。
- 失败重试。
- 死信队列。
- 幂等处理。
3. 重复消费和幂等
MQ 通常很难保证“绝对只消费一次”。实际系统更常见的是:
text
至少投递一次 + 消费端幂等重复消费原因:
- 消费成功但 ack 失败。
- 消费者超时。
- Broker 重试。
- Rebalance。
幂等方案:
- 数据库唯一索引。
- 幂等表。
- 业务状态机。
- Redis
SETNX去重。 - 消息中携带唯一业务 ID。
4. 顺序消息
顺序消息分两类:
| 类型 | 含义 |
|---|---|
| 全局有序 | 所有消息严格有序 |
| 局部有序 | 同一业务 key 下有序 |
业务上更常见的是局部有序,例如同一个订单的状态流转:
text
创建 -> 支付 -> 发货 -> 完成实现思路:
- 同一业务 key 路由到同一个队列或分区。
- 单线程或串行消费该队列。
- 消费失败时暂停后续消息或做补偿。
代价:
- 并发度下降。
- 某个 key 的慢消息会阻塞后续消息。
5. 消息积压
消息积压表示生产速度长期大于消费速度。
常见原因:
- 消费者处理慢。
- 消费者实例数不足。
- 下游数据库慢。
- 单条消息处理异常反复重试。
- 分区或队列数量太少。
处理方式:
text
先止血:扩容消费者、暂停非核心生产、隔离异常消息
再恢复:临时加速消费、批量消费、增加分区
最后复盘:优化消费逻辑和下游容量6. 延迟消息
延迟消息适合:
- 订单超时取消。
- 支付超时关闭。
- 定时提醒。
- 重试退避。
常见实现:
- MQ 原生延迟消息。
- 死信队列。
- 时间轮。
- Redis ZSET。
- 定时任务扫描。
延迟消息通常不适合追求毫秒级精度,更多是业务级近似延迟。
7. 死信队列
消息进入死信队列的常见原因:
- 消费失败超过最大重试次数。
- 消息过期。
- 队列满或被拒绝。
死信队列的作用:
- 保留异常消息。
- 避免阻塞正常队列。
- 支持人工排查和补偿。
8. 事务消息
事务消息用于解决:
text
本地事务成功
消息也必须最终发出去典型流程:
text
发送半消息
-> 执行本地事务
-> 提交或回滚消息
-> Broker 必要时回查本地事务状态适合最终一致性,不等于强一致分布式事务。
9. MQ 削峰填谷
削峰填谷的核心是让 MQ 承接瞬时流量,消费者按自身能力慢慢处理。
注意点:
- 队列容量要能承受峰值。
- 消费者要可水平扩展。
- 下游数据库仍然要限流。
- 要监控积压量和消费延迟。
10. 理解检查
如何保证消息不丢?
生产者确认、Broker 持久化和多副本、消费者成功处理后 ack,三段都要保证。
如何解决重复消费?
不要幻想完全没有重复,消费端必须做幂等。
如何保证顺序消费?
同一业务 key 路由到同一队列或分区,并串行消费。
消息积压怎么办?
先定位生产和消费速率差,再扩容消费者、隔离异常消息、优化慢逻辑和下游容量。
11. 总结
MQ 的核心不是“发消息”,而是围绕可靠投递、幂等消费、有序性、积压治理和最终一致性做工程设计。理解时要覆盖生产者、Broker、消费者三端。