★ 如何保证消息消费幂等?重复消息如何识别与去重?

结论先行:消息中间件普遍提供"至少一次"投递,重复消息是常态而非异常。保证幂等的关键是给每条消息/每个业务请求找到稳定的幂等键,让重复执行与首次执行产生相同结果。常见实现有数据库唯一约束、Redis SETNX 判重、业务状态机校验与版本号乐观锁,实践中往往组合使用。

一、重复消息从哪里来

  • 生产端重试:发送超时后重发,Broker 可能收到两条;
  • 消费端未确认:消费者处理成功但 ack 前宕机或网络超时,消息被重新投递;
  • 分区再均衡:Kafka rebalance、RabbitMQ 队列重新分配导致同一消息被再次拉取;
  • 处理超时重投:消费处理超时被判定失败后重新入队,本质仍是重复投递。

二、幂等实现方案对比

方案原理适用场景
唯一索引去重表以幂等键建唯一索引,插入冲突即重复订单、流水等强约束场景
Redis SETNX幂等键写缓存,原子占位,处理完删除或保留高并发防重提交
状态机校验只允许合法状态流转,如待支付→已支付有明确状态流转的业务
乐观锁/版本号UPDATE 时带版本条件,影响行数为 0 说明已处理并发更新类操作
结果缓存处理成功后缓存响应,重复请求直接返回首次结果查询结果类接口

三、代码示例

-- 以业务幂等键做唯一约束,重复插入返回 0 行影响
INSERT IGNORE INTO t_msg_dedup (biz_key, status) VALUES ('order:1001:pay', 0);
-- 影响行数为 0 说明已处理过,直接返回成功即可
# 或用 Redis 原子占位:抢到才能继续处理
redis-cli SETNX dedup:order:1001 processing

提示:唯一索引去重表要求业务库与去重表在同一数据库事务内;跨库跨服务时更适合 Redis SETNX 或消息表方案。

常见追问 / 记忆点

  • 追问:幂等键怎么定?答:必须来自业务本身,如订单号、支付流水号、请求号,不能依赖消息自带的随机 ID。
  • 追问:先去重表查再执行还幂等吗?答:查+写要保证原子,靠唯一约束或 SETNX 一次写入判断,而不是先查后写。
  • 追问:处理失败要重试,幂等键何时清理?答:成功后再删除占位或标记终态,失败保留以便下次重试。
  • 记忆点:至少一次投递是前提,业务幂等键 + 唯一约束/原子占位是答案,状态机校验是进阶加分点。
笔记加载中…