RabbitMQ 如何保证消息不丢失?confirm、手动 ack 与持久化怎么配合?
结论先行:消息在 RabbitMQ 中有四个可能丢失的环节:生产者发出前、交换机到队列路由失败、Broker 宕机丢内存消息、消费者处理失败。对应的措施是发布确认(confirm)+ mandatory/备份交换机、队列与消息持久化 + 镜像/仲裁队列、手动 ack + 重试与死信。四段都守住才叫"不丢"。
一、四段可靠性措施对照
| 环节 | 丢失场景 | 对策 |
|---|---|---|
| 生产端 | 发送失败、网络断 | 开启 confirm 模式,nack/超时则重发 |
| 交换机路由 | 无匹配队列被丢弃 | mandatory + ReturnListener,或配置备份交换机 |
| Broker 存储 | 宕机后内存消息丢失 | 队列 durable + 消息 persistent,多副本(仲裁队列) |
| 消费端 | 处理中宕机、处理异常 | 手动 ack,成功才确认,失败重投或进死信 |
二、关键机制要点
- confirm:生产者开启 publisher confirm,消息被交换机/队列接收后异步返回 ack,配合超时重发;
- 持久化:队列声明 durable=true,消息投递模式设为持久化(deliveryMode=2),两者缺一不可;
- 多副本:经典镜像队列或 3.8+ 仲裁队列(quorum queue,基于 Raft),节点宕机不丢已确认消息;
- 手动 ack:关闭自动 ack,业务处理成功才 basicAck,处理失败 basicNack/basicReject(按需 requeue),防止假成功丢消息。
三、代码示例
channel.confirmSelect();
// 持久化消息 + 开启 confirm 后发送
channel.basicPublish(EXCHANGE, RK, MessageProperties.PERSISTENT_TEXT_PLAIN, body);
if (!channel.waitForConfirms()) {
// 未确认,走重发或补偿
}
// 消费端:处理成功才手动确认
boolean success = handle(businessLogic(body));
if (success) { channel.basicAck(deliveryTag, false); }
else { channel.basicNack(deliveryTag, false, true); } // 重投或进死信
常见追问 / 记忆点
- 追问:只持久化队列不持久化消息会丢吗?答:会,队列重启后消息仍可能丢失,必须同时设置消息持久化。
- 追问:手动 ack 后消费者又收到重复消息怎么办?答:ack 与业务之间出现网络问题会重投,靠消费幂等兜底。
- 追问:仲裁队列相比镜像队列好在哪?答:镜像队列是异步复制可能丢消息,仲裁队列用 Raft 同步复制且自愈,官方推荐。
- 记忆点:confirm 管发送、持久化管存储、手动 ack 管消费,三段配齐再补死信兜底。