RabbitMQ 重复消费问题:最通俗易懂的解决方案(幂等性)+ 实战总结
- 一、为什么会出现重复消费?
- 二、核心解决思路:实现**幂等性**
- 三、最常用、最稳定的 3 种解决方案(工作必用)
- 方案1:唯一ID + Redis 分布式锁(生产 90% 场景用这个)
- 方案2:数据库唯一索引(简单业务)
- 方案3:状态机判断(订单/支付最常用)
- 四、三种方案对比(直接背)
- 五、最重要的 3 条规则(必须记住)
- 六、一句话总结(面试标准答案)
🌺The Begin🌺点点关注,收藏不迷路🌺 |
一、为什么会出现重复消费?
RabbitMQ无法保证消息只发一次,只能保证至少发一次。
出现重复消费的常见原因:
- 消费者处理完业务,但没来得及 ACK,连接断了
- 消费者异常、重启、超时
- MQ 重试、重入队
- 生产者重试发送
结果:同一条消息被消费多次。
二、核心解决思路:实现幂等性
什么是幂等?
同一个消息执行多次,结果只生效一次,不会重复扣钱、重复下单。
三、最常用、最稳定的 3 种解决方案(工作必用)
方案1:唯一ID + Redis 分布式锁(生产 90% 场景用这个)
原理:
- 每条消息带一个全局唯一ID(msgId / orderId)
- 消费者先去 Redis加锁
- 加锁成功 → 消费
- 加锁失败 → 说明已经消费过,直接跳过
流程图:
收到消息 → 取唯一ID → Redis加锁 → 加锁成功:执行业务 → ACK → 加锁失败:直接ACK,不处理核心代码(简化版):
// 1. 先拿消息唯一IDStringmsgId=message.getMessageProperties().getMessageId();// 2. Redis 加锁(不存在才设置)Booleanlock=redisTemplate.opsForValue().setIfAbsent("lock:"+msgId,"1",10,TimeUnit.MINUTES);if(lock==null||!lock){// 重复消息,直接确认,不处理channel.basicAck(tag,false);return;}// 3. 正常执行业务// ...// 4. 确认消息channel.basicAck(tag,false);方案2:数据库唯一索引(简单业务)
适用:插入数据、记录日志
给业务表的唯一字段(如 orderId)加唯一索引。
重复插入会报唯一键冲突,捕获后直接 ACK 即可。
方案3:状态机判断(订单/支付最常用)
适用:订单、支付、物流等状态流转
比如订单状态:1=待支付 → 2=已支付
消费时执行:
UPDATEorderSETstatus=2WHEREid=?ANDstatus=1;- 更新行数=0 → 已经处理过,直接跳过
- 更新行数=1 → 正常处理
天然幂等!
四、三种方案对比(直接背)
| 方案 | 适用场景 | 性能 | 推荐度 |
|---|---|---|---|
| Redis 分布式锁 | 所有业务(最强通用) | 极高 | ⭐⭐⭐⭐⭐ |
| 数据库唯一索引 | 插入类数据 | 良好 | ⭐⭐⭐ |
| 状态机更新 | 订单/支付/状态流 | 极高 | ⭐⭐⭐⭐⭐ |
五、最重要的 3 条规则(必须记住)
- 重复消费无法避免,只能靠消费端做幂等!
- 必须使用手动 ACK
- 先判断幂等,再执行业务
六、一句话总结(面试标准答案)
RabbitMQ 重复消费无法避免,解决方法是实现消费端幂等。
最常用方案是全局唯一ID + Redis 分布式锁,确保一条消息只消费一次;
订单类业务可使用状态机更新实现天然幂等。
🌺The End🌺点点关注,收藏不迷路🌺 |