RocketMQ 消费幂等性怎么保证?最全落地方案
·
目录
MQ 天生重复投递、网络重试、负载均衡、主从切换都会重复发消息,不做幂等一定会重复下单、重复扣款、重复数据。
一、先搞懂:为什么会重复消费?
- 消费者处理完业务,网络超时,没给 Broker 返回 ACK → Broker 重试投递
- 消费者重启、服务宕机,位点没提交 → 重启后重新拉旧消息
- 集群负载均衡重分配队列 → 消息被其他实例再消费一次
- 手动重置消费位点、运维补发消息
- 事务消息回查、重试队列多次投递
结论:必须默认每条消息都会被重复消费,代码不能靠 “只会发一次” 赌运气。
二、5 种生产级幂等方案(按推荐排序)
方案 1:唯一索引幂等(最简单、最常用、推荐)
原理
利用数据库唯一主键 / 唯一索引,消息业务唯一标识落库,重复插入直接报错,不产生脏数据。
用什么做唯一键
- 消息自带:
msgId - 业务唯一:订单号、支付单号、设备 ID + 事件号、用户行为 ID
流程
- 消费消息拿到 唯一业务号 /msgId
- 插入业务表,该字段加 唯一索引
- 第一次:插入成功 → 正常处理
- 重复消费:插入报唯一冲突异常 → 直接忽略,不执行业务逻辑
优点
- 实现最简单、零额外中间件、强可靠
- 不用写额外判断逻辑,数据库天然兜底
缺点
- 只适合有数据库落库的业务
- 高并发下唯一索引冲突会有少量 DB 压力
适用场景
订单、支付、充值、业务流水、数据入库类 90% 普通业务
方案 2:Redis 分布式去重(高性能、高并发首选)
原理
消费前先查 Redis:
- key = 唯一标识(msgId / 业务单号)
- 存在 → 已经消费过,直接跳过
- 不存在 → 执行业务,再写入 Redis 并设置过期时间
流程伪代码
String key = "mq:id:" + msgId;
// 1. 先判断是否已处理
Boolean notExist = redisTemplate.opsForValue().setIfAbsent(key, "1", 24, TimeUnit.HOURS);
if (!notExist) {
// 重复消息,直接返回消费成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
// 2. 正常执行业务逻辑
doBusiness();
过期时间设置
设置比业务最大保留周期长一点即可,比如 24 小时、7 天,避免 Redis 无限膨胀。
优点
- 性能极高、扛高并发
- 不依赖数据库唯一索引
- 逻辑清晰、好维护
缺点
- 依赖 Redis
- Redis 宕机 / 数据丢失会短暂失去幂等保护
适用场景
高并发秒杀、消息量大、日志埋点、流水通知、无 DB 强约束场景
方案 3:状态机幂等(业务状态判断)
原理
业务本身有状态,先查状态,已处理就不再执行。
例子:订单状态
- 待处理 → 执行业务、更新为已完成
- 已完成 / 已取消 → 直接跳过
流程
- 根据订单 ID 查订单状态
- if 未处理:执行业务 + 更新状态
- if 已处理:直接返回成功
优点
- 贴合业务逻辑,不用额外加索引、不用 Redis
- 适合有明确状态流转的业务
缺点
- 每个业务要自己写状态判断
- 只适合有状态流转的场景
适用场景
订单履约、库存扣减、流程审批、状态变更类消息
方案 4:全局唯一 ID + 本地消息表(可靠兜底)
原理
单独建一张 MQ 消费记录表:msg_id、业务单号、消费状态、创建时间
- 消费前先查本表
- 已存在 → 跳过
- 不存在 → 开启事务:落消费记录 + 执行业务,同一事务保证原子性
优点
- 最强一致性,事务兜底,不怕 Redis 宕机
- 金融、支付级高可靠
缺点
- 多一张表、多一次 DB 操作,开发稍重
适用场景
支付、对账、资金交易、金融核心链路(不能出半点错)
方案 5:RocketMQ 事务消息自带幂等
原理
事务消息本身设计就是:本地事务执行成功才提交消息,失败则回滚配合业务单号天然就能做到不重复、不不一致。
适用场景
下单扣库存、分布式事务场景,直接用事务消息 + 上面任意一种幂等即可。
三、面试常问:怎么选型?给你一套标准规则
- 普通业务、要简单 → 数据库唯一索引
- 高并发、消息量大 → Redis 去重
- 有明确业务状态流转 → 状态机判断
- 金融支付、零容错 → 本地消息表 + 事务
四、开发必守 3 条规范(防止幂等失效)
- 消费业务逻辑必须无副作用,支持重复执行不脏数据
- 幂等 Key 一定要用全局唯一:msgId / 业务单号,不能用普通字段
- 先判幂等,再执行业务,顺序不能反
五、极简背诵版(面试直接背)
保证 RocketMQ 消费幂等常用五种方式:
- 数据库唯一索引:利用唯一键冲突自动拒重复,最简单通用;
- Redis 防重:setIfAbsent 加过期,高性能高并发;
- 业务状态机:查询业务状态,已处理直接跳过;
- 本地消息表:事务落消费记录,金融级强可靠;
- 事务消息:半消息 + 本地事务回查,保证生产消费一致。
更多推荐



所有评论(0)