Skip to content

消息队列核心问题

理解 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、消费者三端。

Released under the MIT License.