Kafka 的 Exactly-once 是什么?幂等生产者和事务能保证数据库只写一次吗?
下面是一段教学模拟,不是真实面试记录。
🧑💻 面试官:Kafka 支持 Exactly-once,消费后写数据库,还需要幂等吗?
🙋♂️ 我:应该不用,消息只处理一次。
🧑💻 面试官:数据库已经提交,提交消费位点前进程退出,重启后会不会再读这条消息?
🙋♂️ 我:会,这两次提交不在同一个事务里。
🧑💻 面试官:那 Kafka 事务覆盖什么,幂等生产者又解决哪类重复?
Exactly-once 必须说明「在哪个范围内」。Kafka 事务不会自动把外部数据库加入同一次原子提交。
面试速答(60 秒版)
Kafka 幂等生产者主要防止生产重试在相应分区内产生重复记录,不等于自动识别应用重复提交的同一笔业务。
Kafka 事务可以把相关 Kafka 输出和消费位点组合提交,配合 read_committed 等条件,构建 Kafka 内部的恰好一次处理效果。
但消费消息后写外部数据库,数据库提交与 Kafka 位点提交仍可能分开。数据库成功、位点未提交,消息重读时就可能再次写入。
因此,外部写入通常还需要稳定业务键、唯一约束与事务内幂等处理,或采用明确支持的一致性方案。不能只打开 Kafka 事务,就宣布所有系统都只执行一次。

知识点详解:先把一次处理画成几次提交
幂等生产者处理的是生产重试
假设生产者发出记录,Broker 保存了,但响应丢失。生产者重试时,需要避免同一次发送产生重复记录。
幂等生产机制借助生产者身份与序列等信息处理这类情况。它不根据消息正文猜测两个订单是不是同一笔。
应用主动调用两次发送,不能默认被当成一次网络重试去重。业务唯一性与生产层幂等仍是两件事。Kafka 设计文档
Kafka 事务可以把输出与位点放在一起
假设应用从输入 Topic 读取消息,计算后写到输出 Topic,再推进输入消费位点。
事务可以让这一组 Kafka 操作作为一个提交单元。消费者还要使用相应读取模式,让未提交或中止事务的输出不被当成已提交结果。消费配置说明
这不是所有程序指令只运行一次。失败后代码可能重跑,但对外可见效果在约定范围内保持一致。
外部数据库存在一个提交窗口
假设消费消息 M 后更新数据库:
| 时点 | 数据库 | Kafka 位点 |
|---|---|---|
| 读到 M | 尚未修改 | 未推进 |
| 数据库提交 | 修改已生效 | 未推进 |
| 此时退出 | 修改仍存在 | M 可能再次被消费 |
| 重启重读 M | 直接再写可能重复 | 后续才提交 |
先提交位点也有问题:位点成功、数据库还没写时退出,可能漏掉业务处理。
所以不能靠调整先后顺序消除所有窗口。两次独立提交,需要额外设计。

幂等记录要与业务变化一起成立
常见方案使用稳定业务 ID,在数据库事务内确认是否已处理,并执行业务变更,结合唯一约束防止并发重复。
如果在内存里记录已处理,重启后就可能丢失。如果先单独提交幂等标记、业务随后失败,也会留下“记成完成、实际没做”的记录。
关键不是有一张去重表,而是标记与业务结果怎样一致提交。外部支付、邮件还需要对应机制,数据库幂等不自动涵盖它们。

面试官继续追问
Exactly-once 是不是消息只能在网络上传一次?
不是。重试和重放可能发生,讨论的是约定范围内可见效果。应说明成立条件,不是字面解释一次传输。
每次生成一个新随机去重键,行不行?
重试时换新键,就无法识别同一笔业务。需要稳定、对应操作语义的身份。
数据库与邮件能一起保证吗?
不能凭 Kafka 或本地数据库事务自动保证。应分别设计可靠投递、状态与幂等,明确结果未知时怎样恢复。
面试速记卡
- 幂等生产者:处理相应生产重试,不自动去重所有业务重复。
- Kafka 事务:组合 Kafka 输出与消费位点等操作。
- 读取条件:配合 read_committed 等配置核对可见结果。
- 外部数据库:独立提交存在重读与重复写入窗口。
- 业务幂等:稳定身份、唯一约束及一致的事务规则。
公司面试真题
真题根据求职者公开面经整理,题意经过概括,非逐字原话或公司官方题库;本文为 Sunday 的独立解析。
美团 · Java后端 · 实习
Kafka 如何保证消费不丢失且不重复?(题意整理)
4.21美团Java实习一二面面经 ↗
面试记录为 2020-04-21、2020-04-24;原帖编辑于 2020-11-14