第一次接触Spring Kafka时,我被它丰富的配置选项弄得有点懵。特别是消费模式这块,什么single、batch,还有各种确认方式,到底该怎么选?后来在电商项目中踩过几次坑才明白,这就像点餐——快餐适合单点,宴席需要套餐,关键看业务场景。
Spring Kafka的消费模式本质上解决的是"怎么吃"的问题。single模式就像用筷子夹菜,一次处理一条消息,适合对实时性要求高的场景,比如订单支付成功通知。而batch模式更像是用勺子舀汤,批量处理消息,适合日志分析、报表生成这类吞吐量优先的任务。
配置消费模式只需要一个关键参数:
yaml复制spring:
kafka:
listener:
type: single # 或 batch
但实际使用中发现,光选对模式还不够。有次我们线上环境消息积压,排查发现是自动提交间隔设置不合理。这里分享个经验:max.poll.records和auto.commit.interval这对参数需要联动调整。比如批量处理时,如果max.poll.records=100但处理耗时超过提交间隔,就会导致重复消费。
在电商即时通知场景中,我推荐使用RECORD自动确认模式。它的工作方式就像快递签收——每处理完一条消息就自动确认,确保实时性。配置示例:
yaml复制spring:
kafka:
consumer:
enable-auto-commit: false
listener:
type: single
ack-mode: record
对应的消费者代码非常简单:
java复制@KafkaListener(topics = "order-notify")
public void handleOrderNotify(String message) {
log.info("处理订单通知: {}", message);
// 无需手动ack
}
但要注意一个坑:如果处理逻辑抛出异常,消息会被重新投递。有次促销活动时,因为没处理好库存不足异常,导致通知重复发送。后来我们加了幂等处理才解决。
对账报表这类夜间批处理任务,用BATCH模式配合自动确认是绝配。我曾将报表生成时间从2小时缩短到30分钟,关键配置是:
yaml复制spring:
kafka:
consumer:
max-poll-records: 500
fetch-max-wait-ms: 5000
listener:
type: batch
ack-mode: batch
代码处理批量消息时有个技巧:
java复制@KafkaListener(topics = "report-data")
public void handleReportData(List<String> messages) {
messages.forEach(msg -> {
// 并行处理提升效率
CompletableFuture.runAsync(() -> processReportItem(msg));
});
}
实测发现,适当增大fetch.max.wait.ms能让批次更饱满,但设置过长会影响实时性。我们的经验值是5秒,在吞吐量和延迟之间取得平衡。
支付回调这种对可靠性要求高的场景,我必用手动确认。配置要点:
yaml复制spring:
kafka:
listener:
ack-mode: manual_immediate
代码中的确认时机很有讲究:
java复制@KafkaListener(topics = "payment-callback")
public void handlePayment(String message, Acknowledgment ack) {
try {
paymentService.process(message);
ack.acknowledge(); // 业务成功才确认
} catch (Exception e) {
log.error("处理失败,消息将重试", e);
// 不确认,等待重试
}
}
踩过的坑:曾经有同事在finally块中确认消息,导致异常消息也被确认,造成资金损失。现在我们会严格区分成功和失败场景。
库存同步这种批量操作,我常用nack实现局部回滚。比如处理100条消息时第50条失败,可以这样处理:
java复制@KafkaListener(topics = "inventory-sync")
public void handleBatch(List<Message> messages, Acknowledgment ack) {
for (int i = 0; i < messages.size(); i++) {
try {
inventoryService.sync(messages.get(i));
} catch (Exception e) {
ack.nack(i, 3000); // 失败位置前移
return;
}
}
ack.acknowledge();
}
这个方案的妙处在于:
我们的订单系统采用这样的组合:
yaml复制spring:
kafka:
consumer:
max-poll-records: 1 # 单条处理
listener:
concurrency: 10 # 并行消费者
这种配置下,每个订单都能被快速处理,且通过手动确认保证可靠性。监控数据显示,99%的消息能在200ms内完成处理。
用户行为分析采用另一套组合:
yaml复制spring:
kafka:
listener:
ack-count: 1000
ack-time: 1m
这样既保证批量处理的效率,又避免因处理时间过长导致重复消费。我们还在消费者中加了本地缓存,进一步减少数据库压力。
在实施过程中,有几个经验值得分享:
记得有次大促,因为没预见到消息暴涨,消费者频繁rebalance导致服务不可用。后来我们增加了以下配置才稳定:
yaml复制spring:
kafka:
consumer:
max-poll-interval-ms: 300000
heartbeat-interval-ms: 3000
session-timeout-ms: 10000