面试知识库
极高 进阶

消息重复与幂等方案#

一句话答案#

MQ 至少投递一次必有重复,幂等方案:唯一消息 ID+去重表、数据库唯一索引、乐观锁 version、业务状态机。

核心要点

方案:

方案适用
唯一消息ID + Redis/DB去重通用
数据库唯一索引INSERT场景
乐观锁(version)UPDATE场景
状态机有状态流转业务

原则: 消费逻辑本身做幂等,不依赖MQ去重

面试回答(2分钟版)

消息重复是MQ使用中不可避免的问题,因为主流MQ都采用At Least Once语义保证消息不丢,代价就是可能重复投递。产生重复的典型场景包括:生产者发送后没收到Broker的ACK触发重试、消费者处理完消息但提交offset失败导致Rebalance后重复消费。既然MQ层面无法避免重复,就必须在消费端实现幂等。我总结四种方案:第一是唯一消息ID加去重表,生产者为每条消息生成全局唯一ID,消费者处理前先查Redis或数据库判断是否已消费过,这是最通用的方案。第二是数据库唯一索引,适合INSERT场景,比如订单表对订单号建唯一索引,重复插入直接报错捕获异常即可。第三是乐观锁version字段,适合UPDATE场景,SQL加上WHERE version=当前版本条件,并发更新时只有一个成功。第四是业务状态机,利用状态单向流转特性,比如订单已支付状态收到重复的支付消息直接丢弃。核心原则是消费逻辑本身做幂等,不依赖MQ去重,因为MQ只保证不丢不保证不重。

追问与易错

追问方向:

  • “为什么 MQ 不保证 Exactly-Once?”→ 网络不可靠 + 至少投递一次更简单可靠
  • “去重表用什么存?有什么问题?”→ Redis/DB,Redis 有过期风险,DB 有性能问题
  • “幂等性怎么做单元测试?”→ 同一条消息连续调用消费方法两次,断言数据库只有一条记录且状态正确;也可 mock MQ 重复投递场景验证去重表/唯一索引是否生效

易错点:

  • ❌ “让 MQ 保证不重复就行了”——MQ 只保证不丢(At Least Once),重复由消费端处理
  • ❌ “唯一索引就能解决所有重复”——只适合 INSERT,UPDATE 需要乐观锁/状态机