位点提交决定重启后从哪继续
消费位移提交是把「我已经处理到哪里」写回 Kafka 的动作。提交时机直接决定语义边界:先处理成功再提交,故障时最多重复处理;先提交再处理,故障时就会跳过尚未处理的消息。多数数据管道选择前者,用少量重复换取不丢数据。
同步与异步怎么选
同步提交会等待提交结果,逻辑简单、失败可立刻感知,代价是阻塞处理线程;异步提交不等待结果、吞吐更好,但失败只能通过回调处理。常见组合是处理过程中异步提交、在关闭或再均衡前做一次同步提交,兼顾吞吐与可靠性。
提交方式对比
| 方式 | 特点 | 适用 |
|---|---|---|
| commitSync | 阻塞等待结果,可靠但影响吞吐 | 关键链路、关闭前与再均衡前的兜底提交 |
| commitAsync | 不阻塞,失败靠回调 | 高吞吐场景,配合周期性同步提交 |
| 按分区提交 | 可精确到分区粒度 | 多分区处理进度不一致时的精细控制 |
| 自动提交 | 按间隔自动提交已拉取位点 | 对重复不敏感的场景,容易造成静默丢数 |
基本信息
| 关键开关 | enable.auto.commit 决定是否自动提交 |
|---|---|
| 自动提交间隔 | auto.commit.interval.ms 控制自动提交频率 |
| 语义权衡 | 先处理再提交可能重复;先提交再处理可能丢失 |
| 配套手段 | 下游幂等或 upsert,把重复收敛掉 |
| 再均衡影响 | 分区被重新分配前应完成一次提交,避免重复消费整段 |
典型使用场景
- 数仓 ODS 落地:处理成功后按批提交,配合落库端主键去重
- 高吞吐日志处理:异步提交加周期性同步提交,平衡吞吐与可靠性
- 多分区精准控制:不同分区处理进度差异大时按分区提交
- 停机与扩容:实例下线前提交位移,减少新实例的重复消费量
- 回退与补数:配合位移重置重新消费指定区间
处理完成后同步提交(Java)
处理完成后同步提交(Java)
consumer.subscribe(List.of("orders"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> r : records) {
process(r); // 先把数据写进下游并确认成功
}
consumer.commitSync(); // 再提交位移:顺序反过来就会丢数
}按分区提交与关闭前兜底
按分区提交与关闭前兜底
// 每个分区处理完成后分别提交,适合各分区进度差异较大的场景
Map<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>();
for (TopicPartition tp : records.partitions()) {
long nextOffset = records.records(tp).get(records.records(tp).size() - 1).offset() + 1;
offsets.put(tp, new OffsetAndMetadata(nextOffset)); // 提交的是“下一条要读的位置”
}
consumer.commitSync(offsets);
// 关闭前做一次同步提交,避免本次已处理的数据在重启后被重复消费
Runtime.getRuntime().addShutdownHook(new Thread(consumer::close));提交的是「下一条要读的位置」,不是「已读的最后一条」:批处理取最后一条的偏移后必须加一,差一条会造成重复消费或多跳一条。
自动提交与手动提交同时开启会互相干扰:自动提交可能在处理尚未完成时把位移推进,进程崩溃后那批数据就被静默跳过了。明确只用一种方式。