消息可靠性篇:如何保证消息不丢、不乱、不重复

消息可靠性不是一句 MQ 保证就够了,它需要生产者确认、Broker 持久化、消费者 Ack、幂等处理和补偿机制一起工作。

消息可靠性篇:如何保证消息不丢、不乱、不重复

消息队列最容易被误解的一点是:用了 MQ,消息就一定可靠。

真实情况是,消息可靠性覆盖生产、存储、投递、消费、重试、补偿整条链路。任何一个环节处理不好,都可能丢消息、重复消息或乱序。

Rendering diagram...

消息不丢

生产者要确认消息是否成功写入 Broker。Kafka 可以使用 acks=all,RabbitMQ 可以使用 Publisher Confirms,RocketMQ 也有发送结果确认。

Broker 侧要持久化消息,并通过副本或主从机制提升可用性。

消费者侧要在业务处理成功后再 Ack 或提交 Offset。不要一收到消息就确认,否则业务处理失败时消息已经被认为消费完成。

消息不重复

分布式系统里重复消息几乎不可避免。

生产者超时后重试,Broker 可能已经收到第一条消息。消费者处理成功但提交 Offset 失败,下次会重新消费。网络抖动、进程重启、Rebalance 都可能造成重复。

因此消费者必须幂等。

CREATE TABLE processed_messages (
  message_id VARCHAR(128) PRIMARY KEY,
  processed_at TIMESTAMP NOT NULL
);

处理消息前先尝试写入去重表。如果唯一键冲突,说明消息已经处理过,可以直接跳过。

消息不乱

全局有序代价很高,通常只追求业务对象维度有序。

比如同一个订单的 OrderCreated -> OrderPaid -> OrderCancelled 要有序,但不同订单之间没有必要强制有序。

Kafka 可以通过相同 key 进入同一 Partition 实现分区内有序。RocketMQ 也可以让同一业务 key 进入同一队列。

{
  "key": "order_10001",
  "event_type": "OrderPaid"
}

重试和死信

消费失败后可以重试,但不能无限重试。无限重试会阻塞正常消息,也会掩盖真实问题。

常见策略是:

  • 立即重试少量次数。
  • 延迟重试。
  • 超过次数进入死信队列。
  • 告警并人工修复。

可靠性分层

问题解决手段
发送失败生产者重试、发送确认
Broker 宕机持久化、副本、高可用
消费失败Ack、重试、死信
重复消费幂等、去重表、业务唯一键
顺序错乱按业务 key 分区
长期不一致对账、补偿任务

小结

消息可靠性要从端到端看。Broker 可靠只是中间一段,真正的业务可靠还要靠生产者确认、消费者幂等、失败重试、死信处理和对账补偿。

参考链接