在 Kafka 里,"消息一致性"一般分三层看:
生产一致性、存储一致性、消费一致性。
Kafka 自身默认是"至少一次",需要配合 幂等生产者 + 事务 + 幂等消费者/业务设计 才能做到"业务上看起来恰好一次"。
一、生产端:怎么保证"消息一定写进 Kafka,而且不乱序/不重复太多"
- 幂等生产者(idempotent producer)
- 开启:enable.idempotence=true
- 原理:Producer 给每个消息带上 (producerId, sequenceNumber),Broker 只接受没见过的序号,自动去重。
- 效果:在 重试场景下避免重复消息,并保证同一分区内消息单调有序。
- 可靠写入配置
- acks=all:要求所有 ISR 副本确认才算成功
- retries > 0:失败自动重试
- max.in.flight.requests.per.connection 通常配合设为 1~5,避免乱序
> 面试可说:"我们生产端开启了幂等和 acks=all,保证消息写 Kafka 时不会因为重试产生重复或丢失。"
二、存储端:Kafka 自己的一致性保证
- 副本机制:一个 Leader,多副本 Follower,同步复制
- 只有写入 ISR(in-sync replicas) 成功的才对消费者可见
- Broker 崩了可以从 ISR 副本中选新 Leader,避免已确认的消息丢失
这块你简单一句就行:"依赖 Kafka 的副本 + ISR 机制保证已确认消息不丢。"
三、消费端:offset 与业务数据的一致性(难点)
Kafka 默认只保证消息至少被消费一次:
- 因为 offset 提交的时机不对,会导致:
- 先提交 offset 后处理失败 → 消息丢失
- 先处理成功但没来得及提交 offset 就挂了 → 消息重复消费
常见做法:
1)"处理后再提交 offset" + 幂等业务逻辑(最常用)
- 流程:
- 拉消息
- 处理(写 DB 等)
- 成功后提交 offset
- 处理失败就不提交,下次重试 → 至少一次
- 业务端通过 幂等键(如业务 ID、去重表、唯一约束)来防止副作用重复
> 例:支付结果消费时,根据 orderId 做幂等表/唯一索引,即使重复消息也不会重复更新。
2)Kafka 事务:将"写消息 + 提交 offset"绑在一起(高级一点)
- 开启事务:
- Producer 配置 transactional.id,调用 initTransactions()
- 在事务里:
- send() 生产消息
- sendOffsetsToTransaction() 把消费的 offset 一起提交
- 最后 commitTransaction() 或 abortTransaction()
- 这样可以实现端到端的 EOS(Exactly Once Semantics):
- 要么消息 + offset 一起提交
- 要么都不提交,下次重新消费,但对下游是幂等的
> 面试说法:
> "我们用 Kafka 事务把消费 offset 提交和新消息发送放在一个事务里,再加下游幂等,保证业务上恰好一次。"
四、端到端"业务一致性"的常见设计套路
- Outbox(本地消息表)模式(数据库 → Kafka)
- 本地事务同时写业务表 + outbox 表
- 后台任务从 outbox 表拉消息发 Kafka(保证"业务成功就一定发消息")
- 下游幂等表 / 唯一约束(Kafka → 数据库)
- 消费时以业务主键(如 orderId、eventId)做唯一索引/幂等表
- 插入已存在则当作重复消息丢弃
- 重试 + 死信队列
- 消费失败重试几次,仍失败的放 Dead Letter Queue,避免一直卡在主队列