bigywater.tech
Kafka 内部原理 · 07

acks=0/1/all:Producer 到底什么时候认为写成功

acks 决定 Producer 在什么条件下认为一条消息"写成功"。这次做了一个最直接的实验:用只有 1 个副本的 topic,在杀掉那个唯一副本所在 Broker 的同时发送消息,分别看 acks=0 和 acks=all 的反应。

验证:acks=0 报告 500 条全部"发送成功",但一条都没有落地
的 kill -9 和的 500 次 send() 几乎同时发生,客户端在 acks=0 模式下不等待任何服务端
验证:acks=all 在同样场景下抛出可感知的异常,不会悄悄丢数据
同样是杀掉唯一副本所在的 Broker,acks=all 配置下抛出了 TimeoutException,客户端能明

验证:acks=0 报告 500 条全部"发送成功",但一条都没有落地

命令 · java // topic 只有 1 个副本,杀掉这个副本所在的 Broker,跟发送消息同时进行 KafkaProducer<String,String> producer = new KafkaProducer<>(props); // acks=0 for (int i = 0; i < 500; i++) { producer.send(new ProducerRecord<>("acks0-loss-demo", "msg-" + i)); // fire-and-forget,不调用 get() }
真实实测 · 本机执行输出
客户端认为已经发送了 500 条(acks=0,没有等待任何服务端确认)

--- 重启被杀掉的 Broker 后,用 kafka-get-offsets.sh 查这个 topic 的 log end offset ---
acks0-loss-demo:0:0

的 kill -9 和的 500 次 send() 几乎同时发生,客户端在 acks=0 模式下不等待任何服务端响应,所以全部 500 次调用都"成功"返回了——没有一次抛异常、没有一次超时。但重启 Broker 后用 kafka-get-offsets.sh 查这个分区的 Log End Offset,结果是 0:一条消息都没有落地。这不是模拟出来的数字,是杀进程、重启、查 offset 得到的结果——acks=0 的"成功"只代表"客户端把字节交给了操作系统的网络层",跟"数据有没有真的到 Broker、有没有真的落盘"完全是两回事。

验证:acks=all 在同样场景下抛出可感知的异常,不会悄悄丢数据

命令 · java // 同样的单副本 topic,同样杀掉这个副本所在的 Broker KafkaProducer<String,String> producer = new KafkaProducer<>(props); // acks=all, delivery.timeout.ms=5000 try { producer.send(new ProducerRecord<>("acksall-loss-demo", "msg-" + i)).get(); } catch (Exception e) { System.out.println("失败: " + e.getCause()); }
真实实测 · 本机执行输出
第一次失败: org.apache.kafka.common.errors.TimeoutException: Expiring 1 record(s) for acksall-loss-demo-0:5000 ms has passed since batch creation. The request has not been sent, or no server response has been received yet.

同样是杀掉唯一副本所在的 Broker,acks=all 配置下抛出了 TimeoutException,客户端能明确知道"这条消息没有被确认",可以选择重试、报错、走降级逻辑。这跟 acks=0 的行为是的、可复现的差别:不是"哪个更快"的取舍,而是"故障发生时,客户端到底知不知道"的根本区别。

结论

实验直接回答了"Producer 什么时候认为写成功":acks=0 认为"发出去就算成功",哪怕目标 Broker 在消息真正到达前就已经挂了,客户端也不会知道——这次测出 500 条"成功"对应 0 条落地;acks=all 会等到确认失败,用一个可以被捕获处理的异常告诉调用方"这条没写成功"。选 acks 不是选性能数字,是选"故障发生时,你的系统会不会有一段时间对丢数据一无所知"。

分步演示:故障发生的那一刻,客户端到底"知不知道"

每一步都是本文上两节杀进程、查offset、捕获异常得到的原始结果。

同样杀掉唯一副本所在的Broker,acks=0和acks=all给出完全不同的"我知道吗"
这一步新验证的结果

    参考与说明

    • 本文两组实验都使用的单副本(replication-factor=1)topic,这样"杀掉那唯一的 Broker"能确定性地制造出"消息目的地完全不可用"的场景,不依赖运气;的 kill -9 和的 kafka-get-offsets.sh/异常捕获是这次证据的来源。
    • 没有涉及:acks=1(只要 Leader 确认,不等其它副本)在 Leader 确认后立刻失去数据(比如 Leader 写完还没同步给 Follower 就崩溃)的场景——这个需要在多副本拓扑下精确控制同步时序才能复现,留给副本同步相关的文章。
    • 之前尝试过对比 acks=0/1/all 三种配置在无故障情况下的平均延迟,本地回环网络下三者差异很小(6.7ms-7.3ms 之间),没有测出有意义的区别——如实记录这个边界:acks 的延迟代价在跨机房网络下会更明显,本机测试测不出这个维度。
    京ICP备2026067502号-1京公网安备11010502063487号