目录

一、先搞懂:为什么会重复消费?

二、5 种生产级幂等方案(按推荐排序)

方案 1:唯一索引幂等(最简单、最常用、推荐)

原理

用什么做唯一键

流程

优点

缺点

适用场景

方案 2:Redis 分布式去重(高性能、高并发首选)

原理

流程伪代码

过期时间设置

优点

缺点

适用场景

方案 3:状态机幂等(业务状态判断)

原理

流程

优点

缺点

适用场景

方案 4:全局唯一 ID + 本地消息表(可靠兜底)

原理

优点

缺点

适用场景

方案 5:RocketMQ 事务消息自带幂等

原理

适用场景

三、面试常问:怎么选型?给你一套标准规则

四、开发必守 3 条规范(防止幂等失效)

五、极简背诵版(面试直接背

MQ 天生重复投递、网络重试、负载均衡、主从切换都会重复发消息,不做幂等一定会重复下单、重复扣款、重复数据。

一、先搞懂:为什么会重复消费?

  1. 消费者处理完业务,网络超时,没给 Broker 返回 ACK → Broker 重试投递
  2. 消费者重启、服务宕机,位点没提交 → 重启后重新拉旧消息
  3. 集群负载均衡重分配队列 → 消息被其他实例再消费一次
  4. 手动重置消费位点、运维补发消息
  5. 事务消息回查、重试队列多次投递

结论:必须默认每条消息都会被重复消费,代码不能靠 “只会发一次” 赌运气。


二、5 种生产级幂等方案(按推荐排序)

方案 1:唯一索引幂等(最简单、最常用、推荐)

原理

利用数据库唯一主键 / 唯一索引,消息业务唯一标识落库,重复插入直接报错,不产生脏数据。

用什么做唯一键

  • 消息自带:msgId
  • 业务唯一:订单号、支付单号、设备 ID + 事件号、用户行为 ID

流程

  1. 消费消息拿到 唯一业务号 /msgId
  2. 插入业务表,该字段加 唯一索引
  3. 第一次:插入成功 → 正常处理
  4. 重复消费:插入报唯一冲突异常 → 直接忽略,不执行业务逻辑

优点

  • 实现最简单、零额外中间件、强可靠
  • 不用写额外判断逻辑,数据库天然兜底

缺点

  • 只适合有数据库落库的业务
  • 高并发下唯一索引冲突会有少量 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:状态机幂等(业务状态判断)

原理

业务本身有状态,先查状态,已处理就不再执行

例子:订单状态

  • 待处理 → 执行业务、更新为已完成
  • 已完成 / 已取消 → 直接跳过

流程

  1. 根据订单 ID 查订单状态
  2. if 未处理:执行业务 + 更新状态
  3. if 已处理:直接返回成功

优点

  • 贴合业务逻辑,不用额外加索引、不用 Redis
  • 适合有明确状态流转的业务

缺点

  • 每个业务要自己写状态判断
  • 只适合有状态流转的场景

适用场景

订单履约、库存扣减、流程审批、状态变更类消息


方案 4:全局唯一 ID + 本地消息表(可靠兜底)

原理

单独建一张 MQ 消费记录表msg_id、业务单号、消费状态、创建时间

  • 消费前先查本表
  • 已存在 → 跳过
  • 不存在 → 开启事务:落消费记录 + 执行业务,同一事务保证原子性

优点

  • 最强一致性,事务兜底,不怕 Redis 宕机
  • 金融、支付级高可靠

缺点

  • 多一张表、多一次 DB 操作,开发稍重

适用场景

支付、对账、资金交易、金融核心链路(不能出半点错)


方案 5:RocketMQ 事务消息自带幂等

原理

事务消息本身设计就是:本地事务执行成功才提交消息,失败则回滚配合业务单号天然就能做到不重复、不不一致。

适用场景

下单扣库存、分布式事务场景,直接用事务消息 + 上面任意一种幂等即可。


三、面试常问:怎么选型?给你一套标准规则

  1. 普通业务、要简单 → 数据库唯一索引
  2. 高并发、消息量大Redis 去重
  3. 有明确业务状态流转状态机判断
  4. 金融支付、零容错本地消息表 + 事务

四、开发必守 3 条规范(防止幂等失效)

  1. 消费业务逻辑必须无副作用,支持重复执行不脏数据
  2. 幂等 Key 一定要用全局唯一:msgId / 业务单号,不能用普通字段
  3. 先判幂等,再执行业务,顺序不能反

五、极简背诵版(面试直接背)

保证 RocketMQ 消费幂等常用五种方式:

  1. 数据库唯一索引:利用唯一键冲突自动拒重复,最简单通用;
  2. Redis 防重:setIfAbsent 加过期,高性能高并发;
  3. 业务状态机:查询业务状态,已处理直接跳过;
  4. 本地消息表:事务落消费记录,金融级强可靠;
  5. 事务消息:半消息 + 本地事务回查,保证生产消费一致。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐