视频看了几百小时还迷糊?关注我,几分钟让你秒懂!
一、什么是"提交"(Commit)?
在 Kafka 中,消费者消费消息后需要"提交偏移量"(offset commit),告诉 Kafka:"我已经处理到第 X 条消息了,下次从 X+1 开始给我发"。
- 偏移量(offset):每条消息在分区中的唯一序号。
- 提交(commit):记录当前已成功处理的 offset。
Kafka 提供两种提交方式:
- ✅ 自动提交(auto-commit):由 Kafka 客户端自动完成(默认开启)
- ✅ 手动提交(manual commit):由开发者代码控制
今天我们重点讲 自动提交机制 ------ 它看似简单,但用不好会丢消息 或重复消费!
二、自动提交如何工作?(原理图解)
默认配置(Spring Boot 中):
spring:
kafka:
consumer:
enable-auto-commit: true # 默认就是 true!
auto-commit-interval: 5000 # 每 5 秒提交一次
🔄 工作流程:
- 消费者启动,从上次提交的 offset 开始拉取消息(比如 offset=100)
- Kafka 客户端后台线程 每隔
auto.commit.interval.ms(默认 5 秒)自动提交当前已拉取的最新 offset - 注意:不是"处理完才提交",而是"拉取了就可能提交"!
⚠️ 关键问题:自动提交的是"已拉取"的 offset,不是"已处理"的 offset!
三、需求场景 + 反例演示(为什么会丢消息?)
🎯 场景:用户注册后发送欢迎邮件
你希望:只有邮件真正发送成功,才算消息消费成功。
❌ 反例代码(危险!):
@KafkaListener(topics = "user-register")
public void handleRegister(User user) {
// 1. 拉取消息(offset=101 被拉取)
// 2. 自动提交线程将在 5 秒内提交 offset=101
try {
emailService.sendWelcomeEmail(user); // 耗时 8 秒,且可能失败
} catch (Exception e) {
// 邮件发送失败!但 offset 已经被自动提交了!
log.error("邮件发送失败", e);
}
}
💥 后果分析:
| 时间 | 事件 |
|---|---|
| T=0s | 拉取 offset=101 的消息 |
| T=3s | 开始发送邮件(耗时 8 秒) |
| T=5s | 自动提交线程提交 offset=101(即使邮件还没发完!) |
| T=6s | 应用崩溃 / 重启 |
| T=7s | 重启后从 offset=102 开始消费 → offset=101 的消息永远丢失! |
🔥 这就是消息丢失的典型原因!
四、正确做法:关闭自动提交,改用手动提交
✅ 步骤 1:关闭自动提交(application.yml)
spring:
kafka:
consumer:
enable-auto-commit: false # 关闭自动提交!
group-id: email-service
✅ 步骤 2:使用手动 ACK(确认)
@KafkaListener(
topics = "user-register",
groupId = "email-service"
)
public void handleRegister(User user, Acknowledgment ack) {
try {
emailService.sendWelcomeEmail(user);
// 业务成功 → 手动提交 offset
ack.acknowledge();
} catch (Exception e) {
// 失败时不提交,下次重启还会重新消费这条消息
log.error("处理失败,不提交 offset", e);
// 可选:记录到死信队列,避免无限重试
}
}
✅ 步骤 3:启用手动 ACK 模式(关键!)
@Configuration
@EnableKafka
public class KafkaConfig {
@Bean
public ConcurrentKafkaListenerContainerFactory<String, User> kafkaListenerContainerFactory(
ConsumerFactory<String, User> consumerFactory) {
ConcurrentKafkaListenerContainerFactory<String, User> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory);
// 设置为手动提交
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
return factory;
}
}
✅ 这样:只有
ack.acknowledge()被调用,offset 才会提交,确保"处理成功才提交"。
五、自动提交 vs 手动提交 对比表
| 特性 | 自动提交(auto-commit) | 手动提交(manual commit) |
|---|---|---|
| 默认开启 | ✅ 是 | ❌ 否 |
| 提交时机 | 每隔 N 秒(与业务无关) | 由代码控制(业务成功后) |
| 消息可靠性 | 可能丢消息 | 不丢消息(至少一次) |
| 重复消费 | 不会(但可能丢) | 可能重复(需幂等) |
| 适用场景 | 日志收集、监控等允许丢失的场景 | 订单、支付、邮件等关键业务 |
六、注意事项(避坑指南)
- 不要盲目依赖自动提交:除非你能接受消息丢失。
- 手动提交也要防重复 :因为应用可能在
ack.acknowledge()之后、实际业务完成前崩溃(极小概率),所以消费者逻辑要幂等(如用数据库唯一索引去重)。 - 批量提交更高效 :如果吞吐量大,可用
AckMode.BATCH,一批消息处理完再统一提交。 - 测试崩溃场景 :用
kill -9模拟非优雅关闭,验证消息是否丢失。
七、总结
- Kafka 的自动提交机制是"拉取即提交",不是"处理完成才提交"。
- 在关键业务中必须关闭自动提交,改用手动 ACK + 幂等设计。
- Spring Boot 提供了完善的 API 支持手动提交,只需几行配置即可提升系统可靠性。
视频看了几百小时还迷糊?关注我,几分钟让你秒懂!