极高 进阶
消息重复与幂等方案#
一句话答案#
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 需要乐观锁/状态机