3、Kafka消息传递语义:At most once、At least once、Exactly once 的区别与适用场景

聊到Kafka的消息传递语义,很多刚接触的朋友会觉得有点绕。说白了,这其实就是在问一个问题:你的消息到底能保证送到几次?

我做了这么多年金融系统,见过太多因为语义选错而翻车的案例。今天咱们就把这三种语义掰开揉碎了讲清楚。

3.1 三种语义的核心区别

先看一张图,帮你快速建立整体认知:

Kafka 三种消息传递语义对比 生产者 Broker 消费者 发送消息 消费消息 At most once 最多一次 可能丢失消息 不重试,不重发 适用:日志、监控 At least once 至少一次 可能重复消息 失败重试,保证送达 适用:金融交易 Exactly once 精准一次 不丢不重 事务+幂等 适用:对账、结算 语义强度:At most once < At least once < Exactly once

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、消费者三方配合:

  1. 生产者开启幂等性(enable.idempotence=true)
  2. 生产者使用事务API发送消息
  3. 消费者开启事务隔离级别(isolation.level=read_committed)
  4. 消费者使用事务性提交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,消费端却忘了做幂等,结果该重复还是重复。

说白了,选哪种语义,取决于你的业务能容忍丢消息还是能容忍重复消息。金融系统里,我们通常选择宁可重复,不能丢失,然后通过幂等去重来解决重复问题。

专注资料整理