这是 Kafka 和很多消息系统最不同的地方。
在很多队列系统里,你容易形成一种直觉:
消息被消费了 -> 消息就没了Kafka 不是这样。
Kafka 的核心问题不是“删消息”,而是:
每个 consumer group 现在读到哪一个 offset 了。
这个“读到哪”,就是消费位点,通常叫:
current offsetcommitted offset
#committed offset 是什么
它表示:
这个 consumer group 下次从哪里继续读。
而且有个非常关键的细节:
提交的 offset,应该是“下一条要读的 offset”,不是“刚处理完那条的 offset”。
所以代码里常见的是:
consumer.commitSync(Map.of(
new TopicPartition(record.topic(), record.partition()),
new OffsetAndMetadata(record.offset() + 1)
));注意这里的 + 1。
#为什么要在“处理成功后”再提交
看这条时间线:
poll 拉到消息 -> 执行业务处理 -> commit offset如果你在处理前就提交:
poll -> commit -> 处理时宕机那就可能丢消息,因为 Kafka 以为你已经读过了。
如果你在处理后提交:
poll -> 处理成功 -> commit那宕机时最坏情况通常是:
- 这条消息会被重新读一次
- 也就是重复消费
所以大多数系统宁可接受“可能重复”,也不接受“直接丢失”。
这就是 Kafka 里最常见的处理语义来源:
at-least-once(至少一次)