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 管消费,三段配齐再补死信兜底。
笔记加载中…