Skip to content
Kafka 可靠性与故障治理 · 第 3 篇 / 共 4 篇
领域数据与中间件
专题Kafka 专题
当前序列Kafka 可靠性与故障治理
阅读位置第 3 篇 / 共 4 篇当前专题第 2 个序列 / 共 6 个序列

Kafka 重复消费治理案例:订单消息重复扣减库存时怎么做幂等

Kafka 很强,但它不是“天然不会重复”的系统。

真正到了线上,重复消费很常见,尤其体现在:

  • 扣库存重复
  • 发券重复
  • 积分重复增加

场景

订单支付成功后发送一条扣减库存消息。

某次线上抖动后,库存服务出现了重复扣减,排查发现:

  • 消费端业务执行成功了
  • 但提交位点前进得不稳定
  • 实例重启后同一条消息被再次消费

先说结论

Kafka 重复消费并不罕见,真正重要的是:

  • 接受重复可能发生
  • 在业务侧把幂等做实

更实用的思路通常是:

  1. 先搞清重复发生在哪个阶段
  2. 再明确幂等键是什么
  3. 最后把“去重记录”和“业务状态变更”绑到同一个事务边界里

一、为什么会重复消费

最常见的场景有:

  • 业务处理成功,但还没来得及提交 offset
  • 消费过程超时或抛异常,随后重试
  • Rebalance 期间同一批消息被重新分配
  • 消费端自己做了重试,但业务操作不是幂等的

所以要先接受一个事实:

  • Kafka 更偏向可靠投递
  • 不是天然只处理一次

二、不要把“幂等”理解得太抽象

幂等的核心问题其实很具体:

  • 同一条业务事件重复执行,结果还能不能保持正确

比如扣库存:

  • 正确结果应该是扣一次

如果一条消息来了两次,你的系统还扣了两次,那幂等就没做好。

三、幂等键到底怎么选

这一步非常关键。

比较常见的选择有:

  • 订单号
  • 订单号加事件类型
  • 业务流水号
  • 消息唯一 ID

真正选择时要优先看:

  • 什么字段能稳定代表“这是同一件业务事”

如果只用 Kafka 分区位点做幂等键,通常不稳,因为它更像消息系统内部坐标,不一定适合业务语义。

四、为什么只查一次数据库还不够

很多实现会这么做:

  1. 先查这笔业务处理过没有
  2. 没处理过就执行
  3. 执行完再标记已处理

问题在于并发或重试下,这三步如果不在同一个事务边界里,就可能出现:

  • 两个消费者都查到“没处理过”
  • 然后都执行成功

所以更稳妥的方式通常是:

  • 用唯一约束、状态机或去重表,把去重和业务变更绑定起来

五、一个更实用的落地思路

1. 建立业务去重键

让系统能明确判断:

  • 这条消息代表的是不是已经处理过的业务事件

2. 去重记录和业务更新一起提交

比如:

  • 插入去重表成功才继续执行业务
  • 或利用业务表唯一约束保证重复事件不会二次生效

3. 失败时明确区分“可重试”和“不可重试”

不是所有异常都该无限重试。

如果是数据脏了、字段非法、业务状态不满足,再怎么重试也没意义。

4. 做好重试次数和死信兜底

这样才能避免重复消息把消费线程长期拖住。

六、一个典型复盘怎么理解

比如库存扣减重复的根因可能是:

  • 消费业务执行成功
  • 数据库也提交成功
  • 但提交 offset 前实例重启

实例恢复后,这条消息再次被拉取,结果再次扣库存。

如果库存扣减逻辑没有按订单号做幂等,问题就会真正落地成数据错误。

七、最容易踩的坑

1. 以为开启幂等生产者就万事大吉

幂等生产者主要解决的是:

  • 生产端重复写入风险

它不等于消费端业务天然幂等。

2. 把幂等做到缓存里

缓存适合做加速,不适合做最终一致的幂等判断。

关键去重信息最好还是落到更稳定的存储里。

3. 无限制重试

如果业务本身不幂等,无限制重试只会把错误放大。

一句话总结

Kafka 重复消费不是异常,而是需要被正面设计进去的常态。

真正关键的不是“怎么保证绝不重复”,而是:

  • 就算重复来了
  • 业务结果也依然正确
延伸阅读相关文章优先当前专题,再补跨专题关联。
同一序列 · 顺着当前主线继续读Kafka 消息积压排查适合把顺序性、幂等、重试、死信和消息积压放在一条线上连续看。Kafka 专题 · Kafka 可靠性与故障治理同一序列 · 回看前文会更完整Kafka 重试与死信怎么设计适合把顺序性、幂等、重试、死信和消息积压放在一条线上连续看。Kafka 专题 · Kafka 可靠性与故障治理同专题其他序列 · 共享标签:案例排障Kafka Broker 磁盘打满前有哪些信号适合把 Rebalance、offset 提交、重试层级和 Broker 磁盘状态放在同一条 Kafka 消费治理主线上看。Kafka 专题 · Kafka 消费治理与运维观察同专题其他序列 · Kafka 投递链路与日志存储机制保留策略和日志压缩适合哪些场景适合把副本机制、acks、批量压缩和日志组织放在一条 Kafka 投递主线上理解。Kafka 专题 · Kafka 投递链路与日志存储机制跨专题关联 · 同场景:线上排障磁盘打满后为什么删除文件不一定立刻生效适合把 Redis 抖动、MySQL 连接打满、MQ 积压、ES 发黄、ClickHouse 合并堆积和磁盘打满放在同一条基础设施排障主线上看。数据与基础设施排障专题 · 数据与基础设施故障的分层排查跨专题关联 · 同场景:线上排障大 Header、buffer、timeout 问题怎么排查适合把 location 匹配、缓冲区和负载均衡放在一起看。容器与站点部署专题 · Nginx 进阶路由与代理治理
继续阅读Kafka 可靠性与故障治理当前序列第 3 篇 / 共 4 篇当前专题第 2 个序列 / 共 6 个序列
往前看
上一篇Kafka 重试与死信怎么设计回到当前序列上一章上一序列消息系统选型与 Kafka 基础从第 1 篇开始:MQ 为什么要用,以及怎么保证消息可靠
往后看
下一篇Kafka 消息积压排查继续当前序列下一章下一序列Kafka 高级投递与消费治理从第 1 篇开始:Kafka 分区键怎么设计更稳

把零散经验整理成可查、可复用、可持续更新的企业级知识门户