3、Kafka消息传递语义:At most once、At least once、Exactly once 的区别与适用场景
聊到Kafka的消息传递语义,很多刚接触的朋友会觉得有点绕。说白了,这其实就是在问一个问题:你的消息到底能保证送到几次?
我做了这么多年金融系统,见过太多因为语义选错而翻车的案例。今天咱们就把这三种语义掰开揉碎了讲清楚。
3.1 三种语义的核心区别
先看一张图,帮你快速建立整体认知:
3.2 At most once —— 最多一次
这种语义的意思是:消息最多被送达一次。丢了就丢了,不重试。
工作机制:生产者发送消息后,不管Broker有没有收到,直接继续发下一条。消费者消费完消息后,立即提交offset,不管处理成功与否。
核心特点:性能最高,但可能丢消息。
适用场景:
- 日志采集(丢几条日志无所谓)
- 监控指标上报(偶尔丢一个采样点不影响)
- 非关键性的统计信息
我的经验:我在做日志采集系统时用过这个语义。当时觉得丢几条日志无所谓,结果有一次排查线上问题,正好缺了那几条关键日志...嗯,后来我改成了At least once。
3.3 At least once —— 至少一次
这是Kafka的默认语义。消息至少被送达一次,可能会重复。
工作机制:生产者发送消息后,等待Broker的ack确认。如果没收到ack,就重试发送。消费者处理完消息后,才提交offset。
核心特点:保证不丢消息,但可能重复。
适用场景:
- 金融交易记录(宁可重复,不能丢失)
- 订单处理系统
- 通知推送
避坑指南:我曾经遇到过一个支付系统,用了At least once语义,结果网络抖动导致消息重复发送,用户被扣了两次钱。所以,使用At least once时,消费端一定要做幂等处理。
3.4 Exactly once —— 精准一次
这才是金融系统的终极追求。消息既不丢失,也不重复,精准送达一次。
工作机制:需要生产者、Broker、消费者三方配合:
- 生产者开启幂等性(enable.idempotence=true)
- 生产者使用事务API发送消息
- 消费者开启事务隔离级别(isolation.level=read_committed)
- 消费者使用事务性提交offset
// 生产者端配置
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("enable.idempotence", true); // 开启幂等
props.put("acks", "all");
props.put("transactional.id", "txn-001"); // 事务ID
// 初始化事务
producer.initTransactions();
// 事务内发送消息
try {
producer.beginTransaction();
producer.send(new ProducerRecord<>("orders", key, value));
producer.send(new ProducerRecord<>("payments", key, value));
producer.commitTransaction();
} catch (Exception e) {
producer.abortTransaction();
}
核心特点:不丢不重,但性能开销最大。
适用场景:
- 金融对账系统
- 资金结算
- 账户余额变更
- 任何涉及钱的系统
我的建议:Exactly once不是银弹。我见过有人为了追求精准一次,把整个系统搞得很复杂,性能下降50%以上。其实很多场景下,At least once + 幂等处理就足够了。
3.5 三种语义对比总结
| 特性 | At most once | At least once | Exactly once |
|---|---|---|---|
| 消息丢失 | 可能 | 不会 | 不会 |
| 消息重复 | 不会 | 可能 | 不会 |
| 性能 | 最高 | 中等 | 最低 |
| 实现复杂度 | 简单 | 中等 | 复杂 |
| 典型场景 | 日志、监控 | 交易、通知 | 对账、结算 |
3.6 金融系统的选择策略
在金融系统里,我的经验是分层使用:
- 核心交易链路:用Exactly once。比如订单创建、资金划转,一分钱都不能错。
- 辅助业务:用At least once + 幂等。比如通知推送、状态更新。
- 非关键数据:用At most once。比如操作日志、埋点数据。
重要提醒:Exactly once不是Kafka单方面能保证的。它需要生产端、Broker、消费端、甚至下游系统一起配合。我曾经见过一个项目,生产端配了Exactly once,消费端却忘了做幂等,结果该重复还是重复。
说白了,选哪种语义,取决于你的业务能容忍丢消息还是能容忍重复消息。金融系统里,我们通常选择宁可重复,不能丢失,然后通过幂等去重来解决重复问题。