bigywater.tech
Kafka 内部原理 · 11

Kafka 为什么写磁盘却还能很快

"顺序写磁盘"是解释 Kafka 性能时最常见的说法,但这个说法在现代 SSD 上到底还站不站得住脚?这次在的 SSD 上分别测了"顺序写 vs 随机写"和"每次强制 fsync vs 交给 OS 批量刷盘"两组对照,用数字看看哪个才是真正的原因。

验证:在这台机器的 SSD 上,顺序写和随机写的差距只有 1.21 倍,没有传说中那么夸张
如实报告这个不够"戏剧性"的结果:在这台机器的 SSD 上,测出顺序写只比随机写快 1.21 倍。
验证:真正的大头是"要不要每次强制落盘",测出 11 倍差距
这组对照才是的大头:同样是顺序写,只是要不要在每次写入后强制把数据刷到磁盘,测出了 11 倍的速度差异。

验证:在这台机器的 SSD 上,顺序写和随机写的差距只有 1.21 倍,没有传说中那么夸张

命令 · python # 顺序写:3000 次 x 1KB,每次 os.fsync() # 随机写:预分配同样大小的文件,3000 次 x 1KB 随机偏移写入,每次 os.fsync() fd = os.open(path_seq, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) for i in range(N): os.write(fd, data) os.fsync(fd)
真实实测 · 本机执行输出
顺序写 3000 次 x 1024 字节,每次 fsync:0.105s,28487.6 次/秒
随机写 3000 次 x 1024 字节,每次 fsync:0.128s,23514.6 次/秒
倍数差异:1.21x

如实报告这个不够"戏剧性"的结果:在这台机器的 SSD 上,测出顺序写只比随机写快 1.21 倍。SSD 没有机械硬盘的磁头寻道开销,随机写和顺序写的物理成本本来就没有传统机械盘那么悬殊——"Kafka 靠顺序写比随机 I/O 快很多倍"这个经典解释,放到 SSD 时代的数据面前,说服力比想象中弱得多。这不是说顺序写没有意义(对机械盘、对预读和内核 I/O 调度依然有价值),而是如实指出:如果只用"顺序写"来解释 Kafka 在现代硬件上的性能,这次测的数据并不支持这是主要原因。

验证:真正的大头是"要不要每次强制落盘",测出 11 倍差距

命令 · python # 版本 A:每条写入后 os.fsync()(强制落盘) # 版本 B:只 os.write() 进 page cache,交给 OS 决定何时刷盘,最后统一 fsync 一次 for i in range(N): os.write(fd, data) os.fsync(fd) # 版本 A 每次都有;版本 B 循环里没有这一行
真实实测 · 本机执行输出
每条消息强制 fsync:20000 条,0.785s,25465 条/秒
交给 OS page cache 批量刷盘(只在最后 fsync 一次):20000 条,0.071s,280155 条/秒
倍数差异:11.0x

这组对照才是的大头:同样是顺序写,只是要不要在每次写入后强制把数据刷到磁盘,测出了 11 倍的速度差异。os.write() 默认只是把数据拷进内核的 page cache,这个操作本身非常快(本质是内存拷贝);os.fsync() 才是真正等待数据落盘的地方,涉及的存储设备 I/O 往返。查了这个测试集群里 topic 的默认配置:log.flush.interval.messages=9223372036854775807(也就是 Long.MAX_VALUE)——Kafka 默认压根不会因为写了多少条消息就强制 fsync,几乎完全交给操作系统决定什么时候把 page cache 里的数据刷盘。这个设计选择用数字验证了它的合理性:强制 fsync 拖慢了 11 倍。

验证:在的 Kafka Broker 层面复现同样的方向(差距变小,但依然存在)

命令 · java KafkaProducer<String,String> producer = new KafkaProducer<>(props); // acks=1 for (int i = 0; i < 20000; i++) { producer.send(new ProducerRecord<>(topic, "k" + i, payload)); // payload 200 字节 } producer.flush();
真实实测 · 本机执行输出
topic=flush-default-demo(默认配置,几乎不强制 fsync)发送 20000 条,用时 0.503s,39786 条/秒
topic=flush-forced-demo(设置 flush.messages=1,每条都强制 fsync)发送 20000 条,用时 1.075s,18613 条/秒

把同样的对照搬到完整的 Kafka Broker 上(而不是绕过网络直接测文件系统):新建两个 topic,一个用默认配置,另一个设置 flush.messages=1 强制每条消息都 fsync,用同一个 Java Producer 各发 20000 条消息。默认配置测到 39786 条/秒,强制 fsync 版本只有 18613 条/秒——方向和文件系统层面的测试完全一致(不强制 fsync 更快),只是倍数从 11 倍降到了 2.1 倍,因为这次多了网络往返、序列化、Broker 内部处理等开销,把纯磁盘 I/O 的差距稀释了一部分,但"不强制 fsync 更快"这个结论在两个不同层面(裸文件系统 vs 完整 Kafka Broker)都被数据验证了。

结论

数据把"Kafka 写磁盘为什么还快"这个问题拆成了两个可测量的因素:顺序写在这台机器的 SSD 上只带来 1.21 倍优势,远没有传统说法讲得那么大;真正的大头是"不强制每条消息都 fsync",在裸文件系统层面测出 11 倍差距,在的 Kafka Broker 上(差距被网络等开销稀释后)依然测出 2.1 倍。Kafka 默认把 log.flush.interval.messages 设成 Long.MAX_VALUE,本质是把"数据什么时候真正落盘"这件事交给操作系统的 page cache 去做——这不是偷懒,而是因为 Kafka 的持久性保证主要靠多副本复制而不是单机 fsync(参考《acks=0/1/all》里验证过的语义),敢于把 fsync 这个慢操作交给系统异步完成。这也直接引出下一个问题:数据不强制落盘、先放在 page cache 里,读的时候又是怎么绕开应用层缓存直接用这块内存的?

分步演示:fsync的次数,是怎么决定速度差距的

这里用6条记录做示意(测量用的是20000条,见上面的基准数据),机制完全一致,只是把规模缩小到方便逐步回放。

真正拖慢速度的不是顺序写,是每条都强制fsync
刚发生的写入统一刷盘的这一批

    参考与说明

    • 本文两组底层基准测试都在这台机器的 SSD 上运行(diskutil info 确认 Solid State: Yes),用 Python 的 os.write/os.fsync/os.pwrite 直接控制写入模式和刷盘时机;Kafka 层面的对照用的单分区单副本 topic 加 Java Producer 测量,避免了网络和序列化以外的额外变量。
    • 和《acks=0/1/all:Producer 到底什么时候认为写成功》一起看:那篇验证了 Kafka 靠多副本确认(而不是单机 fsync)来保证数据不丢,这篇验证的是"既然不靠单机 fsync 保证持久性,那默认几乎不 fsync 这个设计选择在性能上值不值"——两篇合起来才是"为什么 Kafka 敢这么设计"的完整证据链。
    • 没有涉及:本文没有测试机械硬盘(HDD)上顺序写和随机写的差距——本机只有 SSD,没有 HDD 设备可以对比,如实说明这是本次测试环境的限制,不代表 HDD 上顺序写的优势也只有 1.21 倍。
    京ICP备2026067502号-1京公网安备11010502063487号