消息可靠性篇:如何保证消息不丢、不乱、不重复
消息可靠性不是一句 MQ 保证就够了,它需要生产者确认、Broker 持久化、消费者 Ack、幂等处理和补偿机制一起工作。
消息可靠性篇:如何保证消息不丢、不乱、不重复
消息队列最容易被误解的一点是:用了 MQ,消息就一定可靠。
真实情况是,消息可靠性覆盖生产、存储、投递、消费、重试、补偿整条链路。任何一个环节处理不好,都可能丢消息、重复消息或乱序。
消息不丢
生产者要确认消息是否成功写入 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 可靠只是中间一段,真正的业务可靠还要靠生产者确认、消费者幂等、失败重试、死信处理和对账补偿。