
从处理与提交的故障窗口,到自动提交、幂等生产和 Kafka 事务,4个时序帮你分清重复消费、重复生产与外部数据库幂等边界。
| 你看到的现象 | 先判断什么 | 面试中的第一句 |
|---|---|---|
同一个 topic-partition-offset 被业务代码处理两次 | 消费者处理成功后,offset 是否还没提交 | 「这是消费端的至少一次重试窗口,我先查处理与提交的时间线。」 |
| 不同 offset,却有相同业务订单号或事件 ID | 上游是否重复发送,或业务是否重复创建事件 | 「这不是 Kafka 自动把同一 offset 复制出来,要查生产端和业务幂等键。」 |
| Kafka 到 Kafka 的结果看起来重复或不可见 | 是否用了事务生产者,以及消费者是否 read_committed | 「先确认事务边界,再判断消费者看到的是已提交数据还是中止事务。」 |
offset 是消费者下次要读取的位置,不是「这条消息已经被业务成功处理」的永久证明。手动提交时,提交的通常是下一条要读取的 offset;因此,业务处理和提交之间天然存在一个故障窗口。2T0 poll 到 offset=42
T1 订单写库成功
T2 commitSync() 返回前,消费者进程崩溃
T3 重启后从 offset=42 继续读取while (running) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(500));
for (ConsumerRecord<String, String> record : records) {
processBusinessSideEffect(record); // 成功后才算处理完成
}
consumer.commitSync(); // 提交下一条要读取的位置
}「我会优先选择先处理、后提交的至少一次语义。因为处理成功到 offset 提交之间存在崩溃窗口,同一条记录可能重放。对于订单或扣款这类外部副作用,我会用业务事件 ID 做幂等约束,再记录处理结果;不会把 Kafka 的 offset 当成数据库事务的替代品。」
enable.auto.commit=true 为什么可能把消息跳过去?enable.auto.commit=true 表示 offset 按 auto.commit.interval 的频率自动提交;如果消费者在下一次 poll() 前没有处理完上一次拿到的全部记录,已提交的位置可能超过实际处理位置。官方 KafkaConsumer 文档明确提醒,这会造成未处理记录被跳过。2poll 到 offset=0~99
处理到 offset=49
自动提交了「下一条从 100 读」
进程崩溃
重启后,offset=50~99 不会再被读取enable.auto.commit=false。poll() 后只处理自己能在本轮完成的记录量。process 的成功条件和提交 map 的数值。「自动提交适合能保证在下一次 poll 前处理完全部记录的场景;只要处理时间和提交时间需要严格对齐,我就关闭自动提交,在业务成功后提交下一条 offset。否则可能不是重复,而是 offset 超前导致漏处理。」
enable.idempotence 默认启用。启用幂等性时,acks 和 retries 也有配套约束,官方 API 建议不要随意覆盖幂等生产者的默认重试设置。3ProducerRecord,并生成了新的业务事件。enable.idempotence=true,按下面的判断顺序说:同一业务事件的两条记录
│
├─ 同一 topic、分区、offset 被再次处理?──> 查消费者提交与重启时间线
│
└─ 不同 offset 的两条记录?──────────> 查事件 ID、生产端重试和业务接口重试isolation.level=read_committed,并关闭自动提交;生产者设置 transactional.id 后,幂等性会自动启用。13Kafka 消费记录 → 写 MySQL → 提交 Kafka offset「Kafka 事务可以把 Kafka 内部的消费 offset 和 Kafka 输出绑定起来,但写外部数据库时还需要外部系统配合。对订单、扣款、库存这类副作用,我会把业务事件 ID 设为幂等键,依靠唯一约束、状态机或 outbox/inbox 之类的协调设计处理重试;不会声称read_committed能回滚已经提交的数据库写入。」
read_committed 控制消费者是否读取已提交的 Kafka 事务数据;它不等于「下游业务只执行一次」。exactly-once 是一段带明确边界的处理语义,不是覆盖所有外部系统的口号。1topic、分区、offset 和业务事件 ID。相同 offset 被重放,和不同 offset 的同业务事件,不是同一类故障。enable.auto.commit,并确认手动提交是否发生在业务成功之后。提交值是否指向下一条 offset,也要一起核对。2enable.auto.commit=false 就不会重复了吗?enable.idempotence=true 能解决消费者重复吗?poll → process → commit → restart,再谈配置。能把故障窗口画出来,比背一串参数名更能证明你真的做过排查。kafka.apache.org
kafka.apache.org
kafka.apache.org
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.