news 2026/4/15 11:49:49

RabbitMQ 如何保证消息不丢失和不重复消费?掌握这 4 个关键点

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RabbitMQ 如何保证消息不丢失和不重复消费?掌握这 4 个关键点

在开发中,我们经常用RabbitMQ来做系统之间的传话筒。

比如用户下单后,通知库存系统减库存、通知物流系统准备发货。

但问题来了:万一消息丢了怎么办?或者同一条消息被处理了两次怎么办?

别担心!只要做好以下几点,就能让 RabbitMQ 变得既可靠安全


消息可能在哪丢?

假设发快递:

  • 你(生产者)把包裹交给快递员(RabbitMQ);
  • 快递员把包裹送到收件人(消费者)手上。

在这个过程中,包裹可能在三个地方出问题:

  1. 你刚寄出,快递员没收到→ 消息没到 RabbitMQ;
  2. 快递员收到了,但仓库门没开→ RabbitMQ 宕机,消息没了;
  3. 收件人签收前手机没电了→ 消费者处理失败,消息丢失。

所以,我们要从发送方、中间方、接收方三处下手!

先配好配置文件(application.yml)

yaml

spring: rabbitmq: host: localhost port: 5672 username: guest password: guest # 开启 publisher confirm 和 return publisher-confirm-type: correlated publisher-returns: true template: mandatory: true # 使 returns 生效 listener: simple: acknowledge-mode: manual # 手动 ACK retry: enabled: false # 我们自己控制重试逻辑 redis: host: localhost port: 6379


1. 声明队列、交换器(持久化!)

创建队列或交换器时,设置durable=true队列持久化。

java @Configuration public class RabbitMqConfig { public static final String ORDER_EXCHANGE = "order.exchange"; public static final String ORDER_QUEUE = "order.queue"; public static final String ORDER_ROUTING_KEY = "order.create"; @Bean public DirectExchange orderExchange() { // durable = true(默认就是 true) return new DirectExchange(ORDER_EXCHANGE, true, false); } @Bean public Queue orderQueue() { // durable = true return new Queue(ORDER_QUEUE, true); } @Bean public Binding binding() { return BindingBuilder.bind(orderQueue()) .to(orderExchange()) .with(ORDER_ROUTING_KEY); } }

2. 生产者发送消息(带唯一 ID + Confirm 回调)

发送消息时,设置 deliveryMode=2 消息持久化。

同时增加异步非阻塞操作,发完消息立刻返回,RabbitMQ 后台异步确认。支持批量确认、单条确认。

java @Service public class OrderProducer { @Autowired private RabbitTemplate rabbitTemplate; // 生成唯一消息ID(实际可用 UUID 或业务ID) public void sendOrderMessage(String orderId) { String msgId = "msg_" + System.currentTimeMillis(); // 简化版唯一ID MessageProperties props = new MessageProperties(); props.setMessageId(msgId); // 设置唯一ID,用于幂等 props.setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 持久化 Message message = new Message((orderId).getBytes(StandardCharsets.UTF_8), props); // 发送并监听 confirm rabbitTemplate.convertAndSend( RabbitMqConfig.ORDER_EXCHANGE, RabbitMqConfig.ORDER_ROUTING_KEY, message, new CorrelationData(msgId) // CorrelationData 用于关联 confirm ); // 设置 confirm 回调 rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (ack) { System.out.println("消息已确认送达 RabbitMQ: " + correlationData.getId()); } else { System.err.println("消息发送失败: " + cause); // 可在此处记录日志、重发、存 DB 等 } }); // (可选)设置 return 回调,处理路由失败 rabbitTemplate.setReturnsCallback(returned -> { System.err.println("消息无法路由: " + new String(returned.getMessage().getBody())); }); } }

生产环境建议把失败的消息存入数据库,由定时任务补偿重发。

到这里很多人以为只要设置了durable=true和deliveryMode=2,消息就万无一失了。

其实不然!RabbitMQ 收到持久化消息后,会先写入内存缓冲区,再异步刷盘(fsync)。

如果在这之间服务器断电,消息还是会丢!

解决方案:

传统方案:镜像队列(Mirrored Queue)多节点备份,但存在脑裂、数据不一致风险。

现代方案(RabbitMQ 3.8+):Quorum Queue(仲裁队列)

  • 基于 Raft 共识算法,强一致性;
  • 自动选主、故障转移;
  • 写入多数节点才返回成功,真正防丢。

在SpringBoot中声明 Quorum Queue:

java

@Bean public Queue quorumOrderQueue() { return QueueBuilder .durable("order.quorum.queue") .quorum() // 关键! .build(); }


3.消费者:手动 ACK + 幂等处理(防重复)

手动 ACK 是什么?等待消费者调用basicAck,收到 ACK 后,才从队列中删除消息。

什么是幂等?一个操作执行一次和执行多次,结果完全相同。

java @Component public class OrderConsumer { @Autowired private StringRedisTemplate redisTemplate; private static final String CONSUMED_KEY_PREFIX = "mq:consumed:"; @RabbitListener(queues = RabbitMqConfig.ORDER_QUEUE) public void handleOrder(Message message, Channel channel) throws IOException { String msgId = message.getMessageProperties().getMessageId(); String orderId = new String(message.getBody(), StandardCharsets.UTF_8); try { // 1.幂等检查:是否已处理过? String key = CONSUMED_KEY_PREFIX + msgId; Boolean hasConsumed = redisTemplate.opsForValue().setIfAbsent(key, "1", Duration.ofHours(24)); if (Boolean.FALSE.equals(hasConsumed)) { System.out.println("重复消息,跳过处理: " + msgId); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); return; } // 2.处理业务逻辑(比如减库存、发短信) System.out.println("正在处理订单: " + orderId); // 模拟业务耗时 Thread.sleep(1000); // 3.业务成功 → 手动 ACK channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); System.out.println("消费成功,已 ACK: " + msgId); } catch (Exception e) { System.err.println("消费失败: " + e.getMessage()); // 拒绝消息,不 requeue(避免死循环),或根据策略决定是否重试 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); // 或者:basicReject + 记录到死信队列 } } }

关键点:

  • setIfAbsent 实现 Redis 分布式锁式去重;
  • 先检查幂等,再处理业务,最后 ACK
  • 异常时用 basicNack 拒绝消息,避免无限重试。

完整可靠性链路图

ini

[生产者] │ ├── 开启 Confirm → 确保消息到达 Broker └── 消息带唯一ID → 用于后续幂等 │ ▼ [RabbitMQ Broker] ├── 队列/交换器持久化 ├── 消息持久化(deliveryMode=2) └── 使用 Quorum Queue(高可用+强一致) │ ▼ [消费者] ├── 手动 ACK(autoAck=false) ├── 先查幂等(Redis/setIfAbsent) ├── 再执行业务 └── 最后 ACK(失败则 NACK 或进死信队列)


常见误区

误区

正确做法

“开了持久化就不会丢”

还需 Confirm + 高可用队列

“自动 ACK 更简单”

自动 ACK 极易丢消息!必须手动

“RabbitMQ 能保证不重复”

不能!必须消费者自己幂等

“消息ID用时间戳就行”

时间戳可能重复!建议用 UUID 或雪花ID


总结

保证 RabbitMQ 消息不丢和不重复,记住这四个关键点:

1. 生产者确认(Confirm)

  • 开启 publisher-confirm,确保消息成功发到 RabbitMQ。

2. 消息持久化

  • 队列和消息都设置成持久化,防止 RabbitMQ 重启后数据丢失。

3. 消费者手动确认(ACK)

  • 关闭自动 ACK,业务处理成功后,再手动确认消息。

4. 消费幂等性

  • 每条消息带唯一 ID,消费者先检查是否处理过,避免重复消费。

简单来说:

  • 防丢失:Confirm + 持久化 + 手动 ACK
  • 防重复:消息唯一 ID + 幂等检查

没有100%的不丢失,只有无限接近99.99%的可靠性。

做好这四点,你的 RabbitMQ 就足够可靠了

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/27 18:04:10

开题报告卡壳?虎贲等考 AI 打破 “框架焦虑”,学术起点一步到位

作为毕业论文的 “敲门砖”,开题报告不仅是对研究方向的核心梳理,更是导师评估研究可行性的关键依据。但多数学生在撰写时陷入 “三重困境”:框架逻辑混乱、研究意义表述模糊、技术路线不清晰,反复修改仍难达学术规范。虎贲等考 A…

作者头像 李华
网站建设 2026/4/15 5:08:04

OpenAI或从亚马逊获得超100亿美元融资

据The Information周二晚间报道,OpenAI Group PBC正在洽谈从亚马逊公司获得至少100亿美元的融资。这家零售和云计算巨头在投资大语言模型初创公司方面并不陌生。它是OpenAI最大竞争对手Anthropic PBC的最大支持者。几周前,亚马逊云科技(AWS&a…

作者头像 李华
网站建设 2026/4/15 7:13:53

基于java的SpringBoot/SSM+Vue+uniapp的供应链管理系统的详细设计和实现(源码+lw+部署文档+讲解等)

文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言 🌞博主介绍:✌全网粉丝15W,CSDN特邀作者、211毕业、高级全…

作者头像 李华
网站建设 2026/4/7 20:28:53

【每日算法】LeetCode 39. 组合总和

对前端开发者而言,学习算法绝非为了“炫技”。它是你从“页面构建者”迈向“复杂系统设计者”的关键阶梯。它将你的编码能力从“实现功能”提升到“设计优雅、高效解决方案”的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习…

作者头像 李华
网站建设 2026/4/12 20:35:14

Rufus和大白菜

1.Rufus适用于台式机装系统(MBR格式,一般台式机是MBR的,legacy模式), Rufus官网 即使是iso镜像,制作完U盘之后,UEFI模式的笔记本需要打开legacy模式才能识别到分区,但是台式机兼容性…

作者头像 李华
网站建设 2026/3/31 20:34:58

【毕业设计】基于springboot+微信小程序的快递代取系统的设计与实小程序(源码+文档+远程调试,全bao定制等)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

作者头像 李华