这篇是我整理 Kafka 笔记时写下的理解。Kafka 的可靠性经常被一句 “Exactly-Once” 概括,但真正落到工程里,它其实是分区、副本、生产者确认、消费者 offset、事务和下游幂等一起构成的语义边界。
我更愿意把问题拆成两句:
- Kafka 在 broker 和 topic 内部能保证什么?
- 端到端数据链路还需要业务系统自己补什么?
01分区决定吞吐,也决定顺序边界
Kafka 的 Topic 会被拆成多个 Partition。每个分区本质上是 append-only commit log,消息在分区内按 offset 递增写入。
这带来两个结论:
- Kafka 可以保证单分区内有序。
- Kafka 不能保证整个 Topic 全局有序。
生产者如果不指定 key,消息会被分散到不同分区以提高吞吐;如果指定 key,Kafka 会根据 key 的 hash 选择分区,让同一个 key 的消息更稳定地进入同一个分区。
因此订单事件、用户行为、支付状态这类需要局部有序的链路,通常要选择合适的 key,例如 order_id 或 user_id。否则后面再谈 Exactly-Once 也没有意义,因为顺序边界一开始就没定义清楚。
02副本与 ISR:可靠写入不是单点成功
Kafka 的每个分区可以有多个副本,其中一个是 Leader,其他是 Follower。读写都走 Leader,Follower 从 Leader 拉取数据保持同步。
ISR 是 in-sync replicas,也就是当前和 Leader 保持同步的一组副本。可靠性配置主要围绕三项:
| 配置 | 作用 |
|---|---|
acks=0 | 生产者不等 broker 确认,吞吐高但可能丢数据 |
acks=1 | Leader 写入成功即返回,Follower 未同步时仍可能丢 |
acks=all | ISR 内副本确认后才返回,可靠性最高 |
min.insync.replicas | 至少多少 ISR 副本可用才允许写入 |
如果业务要求可靠写入,常见组合是 acks=all + 合理的 min.insync.replicas + 禁止 unclean leader election。这样做牺牲可用性和吞吐,但能避免落后副本被选为新 Leader 后造成数据回退。
03Offset 提交:重复和丢失的分界线
消费者真正容易出问题的地方是 offset 提交时机。
如果先提交 offset,再处理业务,处理失败时这批消息不会再被消费,可能丢数据。
如果先处理业务,再提交 offset,提交前消费者宕机,消息会被重新消费,可能重复。
所以 Kafka consumer 默认的自动提交虽然简单,但在严肃链路里通常要关闭:
enable.auto.commit=false然后在业务处理完成后手动提交。即使这样,也只能把语义推进到 at-least-once:消息可能重复,但不应该丢。端到端 exactly-once 还需要下游写入幂等,或者和 Kafka 事务一起设计。
04Kafka 的 EOS 到底覆盖哪里
Kafka 的 Exactly-Once Semantics 主要解决两类问题:
- 幂等生产者:生产者重试不会在同一分区产生重复消息。
- 事务:一组跨分区写入和 offset 提交可以原子提交或回滚。
这对 Kafka Streams 这类“读 Kafka、处理、再写 Kafka”的场景很有价值。它能把消费 offset 和输出 topic 写入放进同一个事务里。
但如果你的链路是:
Kafka -> Spark / Flink / Java Consumer -> MySQL / Doris / Redis / 外部接口Kafka 自己无法保证外部系统的 exactly-once。外部系统必须提供幂等写、唯一键、事务表、去重表或二阶段提交能力。
所以我理解的 EOS 边界是:Kafka 可以保证 Kafka 内部的幂等和事务,但业务链路要自己保证下游副作用的幂等。
05在 Olist 重放设计里的对应理解
Olist 湖仓项目里,我为历史事件重放设计了下面这条链路:
Olist CSV -> Kafka events -> Spark Structured Streaming -> windowed GMV这里最关键的问题不是“Kafka 是否 Exactly-Once”,而是 replay 和 streaming 的比较口径是否一致。
为避免常见的错误,设计里把下面几件事写成了明确约束:
- replay 按
event_time + event_id排序。 - 追加 watermark event 推进最后窗口输出。
- 用
replay_manifest记录实际重放的(order_id, order_item_id)。 - batch 侧只比较 manifest 中的 item。
当前本地环境没有实际拉起 Kafka broker,所以这条链路仍是待补的端到端验证项。我保留 replay_manifest、事件时间和窗口对账的设计,是为了在运行环境补齐后能明确回答“比较的是不是同一批数据”,而不是只拿一组漂亮的 GMV 数字当结论。
这也说明端到端一致性不能只靠 Kafka 配置,还要靠事件时间、窗口、水位线和比较口径一起闭环。
06小结
我现在会用四句话记 Kafka 可靠性:
- 分区决定吞吐和局部顺序。
- ISR、acks 和 min.insync.replicas 决定写入可靠性。
- offset 提交时机决定重复与丢失的风险。
- Exactly-Once 不是一句配置,而是 Kafka 内部事务加下游幂等的系统设计。
面试或项目复盘里,与其直接说“Kafka 支持 Exactly-Once”,不如先说清楚:在哪个边界内 exactly-once,出了 Kafka 之后又靠什么兜底。