RabbitMQ 重复消费解决方案:幂等性保障全攻略(原理+流程图+实战代码)
RabbitMQ 重复消费解决方案幂等性保障全攻略原理流程图实战代码前言一、为什么会出现消息重复消费核心原因1.1 根本原因1.2 3 大常见场景1.3 重复消费流程图二、核心解决方案实现消费端**幂等性**2.1 什么是幂等性2.2 实现幂等性的 4 种通用方案三、方案一唯一ID Redis 分布式锁推荐最稳定3.1 实现原理3.2 幂等性处理流程图3.3 SpringBoot 实战代码1. 生产者发送唯一ID2. 消费者实现幂等消费核心四、方案二数据库唯一索引简单业务4.1 实现原理4.2 适用场景4.3 代码逻辑五、方案三状态机幂等订单/支付业务5.1 实现原理5.2 示例六、三种方案对比生产选择七、生产环境关键注意事项八、总结核心一句话RabbitMQ 重复消费解决口诀The Begin点点关注收藏不迷路前言在 RabbitMQ 生产环境中消息重复消费是无法避免的问题——网络抖动、消费者重启、重试机制、未 ACK 重入队等都会导致同一条消息被多次投递。如果不做处理会造成重复下单、重复扣款、数据翻倍等严重后果。解决重复消费的核心方案不是禁止重复发送而是保证消费端幂等性同一消息执行多次结果只生效一次。本文将从重复消费原因、幂等性方案、流程图、实战代码、生产最佳实践全方位讲解让你彻底解决 RabbitMQ 重复消费问题。一、为什么会出现消息重复消费核心原因1.1 根本原因RabbitMQ 只保证消息至少投递一次At Least Once不保证仅投递一次Exactly Once。1.2 3 大常见场景消费者未 ACK处理完业务但网络断开没发送 ACK → MQ 重发消息生产者重试发送失败重试导致消息发送多次故障转移镜像队列主从切换触发重投1.3 重复消费流程图消息第一次消费成功业务执行成功未发送ACK/网络断开MQ认为消费失败消息重新入队消费者再次消费重复执行业务二、核心解决方案实现消费端幂等性2.1 什么是幂等性同一个操作执行多次结果保持一致不会产生副作用。执行1次成功执行100次依然成功数据不变2.2 实现幂等性的 4 种通用方案唯一ID 分布式锁Redis/MySQL最通用、推荐数据库唯一约束简单场景状态机幂等订单状态流转全局唯一ID判断Redis 存在即跳过生产环境 90% 场景使用唯一ID Redis 分布式锁三、方案一唯一ID Redis 分布式锁推荐最稳定3.1 实现原理生产者发送消息时添加全局唯一IDorderId、msgId、雪花ID消费者拿到消息后先通过 Redis 加锁加锁成功 → 执行业务加锁失败 → 说明已消费直接跳过执行完成释放锁或设置过期时间自动释放3.2 幂等性处理流程图加锁成功加锁失败消费者收到消息获取消息唯一IDRedis SETNX 加锁执行业务逻辑直接丢弃不处理业务执行成功手动ACK确认释放Redis锁3.3 SpringBoot 实战代码1. 生产者发送唯一IDpublicvoidsendOrderMsg(StringorderId){MessagePropertiespropertiesnewMessageProperties();// 设置唯一ID订单ID/消息IDproperties.setMessageId(orderId);MessagemessagenewMessage(订单消息.getBytes(),properties);rabbitTemplate.send(order.exchange,order.rk,message);}2. 消费者实现幂等消费核心RabbitListener(queuesorder.queue)publicvoidreceive(Messagemessage,Channelchannel)throwsIOException{longtagmessage.getMessageProperties().getDeliveryTag();// 1. 获取唯一IDStringmsgIdmessage.getMessageProperties().getMessageId();try{// 2. Redis 加锁NX不存在才设置EX过期时间防止死锁BooleanlockstringRedisTemplate.opsForValue().setIfAbsent(mq:lock:msgId,1,10,TimeUnit.MINUTES);// 3. 加锁失败 已消费直接ACKif(locknull||!lock){channel.basicAck(tag,false);System.out.println(消息已重复消费直接跳过msgId);return;}// 4. 加锁成功执行业务System.out.println(正常消费消息msgId);// 业务逻辑订单、支付、入库...// 5. 手动ACKchannel.basicAck(tag,false);}catch(Exceptione){// 6. 异常拒绝消息重新入队channel.basicNack(tag,false,true);}}四、方案二数据库唯一索引简单业务4.1 实现原理给业务表的唯一标识字段orderId增加唯一索引重复插入时数据库抛出唯一键冲突异常捕获异常直接确认 ACK实现幂等4.2 适用场景简单插入、记录类业务。4.3 代码逻辑try{// 插入数据orderMapper.insert(order);}catch(DuplicateKeyExceptione){// 重复数据直接确认channel.basicAck(tag,false);return;}五、方案三状态机幂等订单/支付业务5.1 实现原理订单有状态待支付 → 已支付 → 已取消消费时先判断状态如果已是已支付直接跳过通过状态流转保证幂等5.2 示例UPDATEorderSETstatus2WHEREid?ANDstatus1;更新成功执行更新0条已处理跳过六、三种方案对比生产选择方案实现难度性能适用场景推荐度Redis 分布式锁中等极高所有场景订单、支付、消息⭐⭐⭐⭐⭐数据库唯一索引简单一般插入类业务⭐⭐⭐状态机幂等中等高订单、支付、状态流转⭐⭐⭐⭐七、生产环境关键注意事项必须开启手动 ACK唯一ID必须全局唯一雪花ID、UUID、业务单号Redis 锁必须设置过期时间10~30分钟防止服务宕机死锁先判断幂等再执行业务不要依赖 RabbitMQ 去重必须在消费端做幂等重复消息直接 ACK不要让它一直重投八、总结核心一句话RabbitMQ 重复消费解决口诀重复消费无法避免只能保证幂等生产首选唯一ID Redis 分布式锁先加锁再消费重复直接跳过手动ACK 幂等判断 安全消费这套方案可以保证在任何异常场景下消息绝对不会重复执行The End点点关注收藏不迷路