Yihui’s Blog

Kafka如何保证消息不重复消费

日期:2026-09-27
标签:#面试 #场景设计 #消息队列 #Kafka
难度:中等
来源:牛面场景题
答案说明:独立整理(站内题目标记为 VIP,未读取会员答案)

一句话答案

Kafka 链路通常允许重复投递;真正要保证的是业务副作用幂等,通过稳定业务键与原子去重,使同一事件重放也只生效一次。

面试口语版(约 75 秒)

我不会承诺消费者绝不会再次读到同一条消息。消费者处理完业务后、提交位点前宕机,重启就可能重读;生产者重试和上游重复发事件也可能带来重复。生产者幂等能限制其发送重试造成的重复写入,但不能覆盖业务侧的重发。消费端应关闭自动位点提交,定义事件 ID 或业务唯一键,在同一个数据库事务里完成“检查并登记已处理”与业务更新,最后由应用提交 Kafka 位点。若事务提交后位点提交失败,重读时唯一约束让更新成为空操作。Kafka 事务能覆盖 Kafka 内部的读、处理、写与位点原子提交;外部数据库和第三方接口要用自身的唯一约束、条件更新或幂等键。

机制与伪代码

sequenceDiagram
  participant K as Kafka
  participant C as 消费者
  participant D as 业务数据库
  K->>C: 交付事件 E
  C->>D: 原子登记 E 并更新业务
  D-->>C: 提交成功
  Note over C,K: 位点提交前宕机,重启后 E 会重读
  K->>C: 再次交付 E
  C->>D: 唯一键判重,不再更新
  C->>K: 确认已处理后提交位点
收到事件 e:
  关闭自动位点提交,由应用控制提交时机
  开始数据库事务
  尝试插入 (consumer_name, e.event_id) 到已处理事件表,唯一约束防重
  如果插入成功:执行业务更新,并提交数据库事务
  如果唯一键已存在:确认之前已处理,结束数据库事务
  只有数据库事务成功或确认已处理后,才提交 Kafka 位点
  其他错误:不提交位点,按策略重试或隔离并告警

伪代码要求去重记录与业务副作用在同一个可用的事务边界中;仅靠 Redis 临时标记或先查后写,遇并发和崩溃可能失效。不同数据库的冲突处理语法需按实际产品实现。

具体失败分支

订单支付事件触发发券。发券事务提交后消费者宕机,位点没有提交;重启再收到同一事件,唯一键“活动 ID + 订单 ID + 券种”冲突,发券逻辑不再执行。若调用的是第三方发券 API,本地去重记录与外部调用不能天然原子提交,应向第三方传幂等键并做结果查询、对账或补偿。

取舍与易错点

  • 采用上述处理后提交位点的流程时,设置 enable.auto.commit=false,由应用明确决定提交时机。
  • 生产者幂等、Kafka 事务、消费者幂等分别解决不同范围的问题;不要把 Kafka 的 exactly once 扩展成“任何外部数据库都只写一次”。
  • 去重键必须稳定且粒度正确;同一业务事件多种处理动作,可把消费者名称或动作类型纳入键。
  • 幂等记录的保留期至少覆盖消息可重放和人工补偿窗口;清理过早会让旧事件重新生效。
  • 条件更新适合状态单调前进的场景,如仅把“待支付”改为“已支付”;副作用复杂时用事务性唯一流水更清楚。

面试官递进追问

  1. 为什么重复消费不可完全避免? 业务提交与位点提交之间存在崩溃窗口。
  2. 生产者 enable.idempotence 能替代消费端去重吗? 不能,它只约束生产者重试写入的范围。
  3. 调用第三方 API 如何防重? 传稳定幂等键,配状态查询、超时重试与对账;若对方不支持,需设计可补偿流程。

自测

  • 按时间顺序演示“业务成功,位点未提交”后为何重放。
  • 设计发券的唯一键,并解释为什么只用消息 offset 可能不够。
  • 说清 Kafka 内部事务和外部数据库事务的边界。

参考资料

核对日期:2026-09-27。

维护与整理 · Yihui在 GitHub 上编辑

继续阅读

浏览全部文章

如何为Redis分布式锁设置合理的超时时间?

日期:2026-09-27 标签:#面试 #场景设计 #Redis 难度:中等 来源:牛面场景题 答案说明:独立整理(站内题目标记为 VIP,未读取会员答案) 一句话答案 租约应覆盖可预期的执行、暂停与网络抖动,同时限制故障后的等待;没有可靠耗时上界时用有身份校验的受控续期,并在业务资源侧防止旧执行者写入。 面试…

阅读全文

怎么用Redis实现可重入的分布式锁?

日期:2026-09-27 标签:#面试 #场景设计 #Redis 难度:中等 来源:牛面场景题 答案说明:独立整理(站内题目标记为 VIP,未读取会员答案) 一句话答案 为同一把锁保存“本次最外层获锁的唯一令牌 + 重入次数 + 租约”;嵌套调用共享该令牌并原子递增,释放时递减,次数归零才删除。新一轮独立获锁必…

阅读全文

基于 Redis 实现分布式锁有什么优缺点?

日期:2026-09-27 标签:#面试 #场景设计 #Redis 难度:简单 来源:牛面场景题 答案说明:独立整理(站内题目标记为 VIP,未读取会员答案) 一句话答案 Redis 锁接入简单、响应快,适合容忍少量故障窗口内重复执行的任务;租约过期与主从切换可能破坏互斥,关键写入还须在资源侧拒绝旧持有者。 面试…

阅读全文