← 返回题目列表

消息消费如何保证幂等,避免重复消费导致数据不一致?

高频 中等 第 11 / 25 题 更新于 2026/07/28
幂等消费重复消费消息一致性

简化版

消息消费要通过业务唯一键、消费记录表、数据库唯一约束、状态机和乐观锁保证幂等。因为 MQ 通常只能保证至少一次投递,消费者必须能接受同一消息重复到达,并保证重复执行不会产生重复扣款、重复发券、重复加积分。

详细版

幂等消费常见方案:

  1. 消费记录表:consumer_group + message_id 唯一。
  2. 业务唯一键:例如 payment_id + points_type
  3. 数据库唯一约束:防止并发重复插入。
  4. 状态机条件更新:只有合法状态才能流转。
  5. 乐观锁版本号:防止旧消息覆盖新状态。
  6. 下游接口也传幂等号:跨服务保证只处理一次。

不建议只依赖 Redis 去重,除非能接受过期或丢失风险。关键业务最好以数据库唯一约束或业务流水作为最终防线。

完整版教学

一、为什么消息会重复消费

消息队列为了可靠性,常见语义是至少一次投递。也就是说,它宁愿让消费者重复收到,也不轻易丢消息。

重复原因很多:

  1. 消费者处理成功,但提交 offset 或 ack 失败。
  2. 消费者处理过程中重启,消息被重新投递。
  3. Broker 重平衡,分区被其他消费者接管。
  4. 生产者重复发送了同一业务事件。

所以消费者不能假设“我只会收到一次”。

二、消费记录表怎么做

消费记录表适合通用去重。

设计唯一键:

consumer_group + message_id

消费开始时先插入记录,插入成功说明第一次消费;如果唯一键冲突,说明已经处理过或正在处理。

但要注意,如果插入消费记录成功后业务处理失败,记录状态要能区分 PROCESSINGSUCCESSFAILED,否则可能误判为已成功。

三、业务唯一键更可靠

很多时候,业务唯一键比消息 ID 更重要。因为同一个业务事件可能被不同消息 ID 重发。

例如支付成功后加积分,真正要防重的是同一笔支付不能加两次积分,所以唯一键应该是:

payment_id + points_scene

即使消息 ID 不同,只要 payment_id 相同,也不会重复加积分。

四、状态机可以避免旧消息覆盖新状态

订单状态消费要特别注意顺序。可能先收到“订单已发货”,后收到延迟的“订单已支付”。

状态机应该规定只能从低阶状态流转到合法下一状态:

WAIT_PAY -> PAID -> SHIPPED -> FINISHED

如果订单已经是 SHIPPED,再收到 PAID 事件,不能把状态改回去。

五、Redis 去重的边界

Redis 去重速度快,适合短期防重,例如防止短时间重复点击。但如果 Redis 数据过期、淘汰或故障,去重信息可能丢失。

关键资金、积分、库存类消费,最好让数据库唯一约束作为最终防线。Redis 可以做第一层过滤,数据库保证最终正确。

六、幂等消费的事务边界

消费记录和业务更新最好在同一个本地事务里完成。比如积分消费场景中,插入消费记录、增加积分、写积分流水应该一起提交。否则可能出现消费记录成功但积分没加,后续消息又被当成已消费跳过。

如果业务更新和消费记录不在同一个库,就要使用可靠消息、补偿或把业务唯一约束放到真正产生副作用的那张表上。幂等不是单独插一条日志就结束,关键是防住最终副作用重复或丢失。

还要注意失败返回语义:已经幂等处理过的重复消息,通常应该返回消费成功,让 MQ 提交进度,而不是抛异常导致它无限重投。

七、常见误区与追问

这道题要紧扣「消费者幂等」本身回答,不能把它混成泛泛的数据一致性套话。面试官通常会追问“写入怎么确认、失败怎么补、旧数据怎么防、成本在哪里”,所以回答要覆盖一致性目标、写入顺序、消息投递、幂等、补偿、对账和读写体验。

回答层次要讲清的内容容易漏掉的边界
核心结论消费者幂等保证同一消息被重复投递或重复消费时,业务结果只生效一次不要停在名词解释
流程机制接收消息 -> 解析业务唯一键 -> 检查去重记录或状态 -> 执行业务更新 -> 记录处理结果 -> 重复消息直接确认要说清触发点、状态变化、确认点和失败兜底
工程取舍同一扣款消息重复投递 3 次,账户余额只能扣一次,后两次应识别为已处理一致性方案本质是在强一致成本、系统可用性、延迟和业务可接受的不一致窗口之间取舍
消费者幂等 面试拆解:
1. 接收消息
2. 解析业务唯一键
3. 检查去重记录或状态
4. 执行业务更新
5. 记录处理结果
6. 重复消息直接确认

记忆钩子:先给结论,再拆流程,再讲数字例子和失败边界;回答「消费者幂等」时要围绕题目问法收束,不要把相邻概念堆成一段没有重点的名词清单。

  • 误区:MQ 能保证消息只投递一次。 多数 MQ 实际提供至少一次投递,重复消费是常态风险。
  • 误区:消费代码不报错就不需要幂等。 ACK 丢失、消费者重启和重平衡都可能导致重复投递。
  • 误区:只按 messageId 去重就够了。 有些重发会生成新 messageId,更稳的是业务唯一键。
  • 追问:常见幂等手段? 去重表、唯一约束、状态机、乐观锁、版本号和业务流水号。
  • 追问:先执行业务还是先写去重? 要放在同一本地事务里,避免去重成功业务失败或反过来。
  • 追问:幂等失败会怎样? 轻则重复通知,重则重复扣款、重复发货或状态回退。

八、加强记忆

消息幂等的核心是“重复消息一定会来,业务结果不能重复”。通用消息 ID 去重能挡一层,业务唯一键和数据库唯一约束更可靠;涉及状态流转时,还要用状态机防止旧消息把新状态覆盖掉。