bigywater.tech
Kafka 内部原理 · 12

Page Cache:Kafka 为什么不自己管理缓存

Kafka 的 Broker 是个 JVM 进程,理论上完全可以在堆内存里自己维护一份"最近消息缓存"。但它没有这么做,而是把这件事完全交给操作系统的 page cache。这次做了一件很直接的事:写入一批数据、测消费速度,然后 kill -9 整个 Broker 进程、重新启动,再测一次同样数据的消费速度——如果这个"缓存"是操作系统级别的,那进程被炸掉重启,读取速度应该不会明显变化。

验证: kill -9 炸掉整个 Broker 进程、重新启动后,读同一批数据的速度基本没变
kill -9 之后,这个 Broker 进程连带它 JVM 堆里可能存在的任何东西全部消失,重新启动是一个全
验证:尝试用 8GB 无关数据制造内存压力驱逐缓存,如实报告没有测出显著变慢
如实报告一个没有达到预期的结果:写入并读取 8GB 完全无关的数据(这台机器总内存 16GB,vm_stat 显

验证: kill -9 炸掉整个 Broker 进程、重新启动后,读同一批数据的速度基本没变

命令 · java KafkaConsumer<String,String> consumer = new KafkaConsumer<>(p); consumer.assign(List.of(tp)); consumer.seekToBeginning(List.of(tp)); long t0 = System.nanoTime(); while (count < end) { ConsumerRecords<String,String> recs = consumer.poll(Duration.ofMillis(2000)); count += recs.count(); }
真实实测 · 本机执行输出
--- 重启前,消费 5000 万字节级别的数据(50000 条 x 2000 字节,约 100MB)---
第一次:消费 50000 条,用时 0.8301s,吞吐 120.5 MB/s
第二次:消费 50000 条,用时 0.8160s,吞吐 122.5 MB/s

---  kill -9 整个 Broker 进程(JVM 堆、进程内任何状态全部销毁),重新启动 ---
--- 重启后,消费同一批数据 ---
消费 50000 条,用时 1.0241s,吞吐 97.6 MB/s

kill -9 之后,这个 Broker 进程连带它 JVM 堆里可能存在的任何东西全部消失,重新启动是一个全新的 JVM 实例,没有任何"记忆"。如果 Kafka 靠自己在进程内维护缓存来加速读取,重启后第一次读应该会明显变慢(缓存被清空,需要重新建立)。测出来的结果是重启后 1.02s,比重启前的 0.83s/0.816s 只慢了大约 25%,量级完全没变——这组对比直接支持了"Kafka 读取这批数据快,靠的不是它自己进程里的什么东西",因为那个"东西"在重启的瞬间已经被销毁了,但读取速度基本没受影响。真正没被这次重启动到的,是操作系统的 page cache——它独立于任何一个进程存在,Broker 进程可以死、可以重启,只要操作系统本身没重启,内核里缓存的这些磁盘数据块就还在。

验证:尝试用 8GB 无关数据制造内存压力驱逐缓存,如实报告没有测出显著变慢

命令 · shell $ dd if=/dev/urandom of=/tmp/evict_big.dat bs=1m count=8000 # 写 8GB $ dd if=/tmp/evict_big.dat of=/dev/null bs=1m # 读一遍,让它过一遍 page cache
真实实测 · 本机执行输出
写入 8GB 无关数据,读回一遍制造内存压力后,重新消费同一批 kafka 数据:
第一次:消费 50000 条,用时 0.8970s,吞吐 111.5 MB/s
第二次:消费 50000 条,用时 0.7799s,吞吐 128.2 MB/s

如实报告一个没有达到预期的结果:写入并读取 8GB 完全无关的数据(这台机器总内存 16GB,vm_stat 显示当时空闲页只剩几千个,说明确实有内存压力),试图把 kafka 那 100MB 数据从 page cache 里挤出去,但重新消费的结果(0.897s)跟之前几次(0.83s/0.816s/1.02s)没有量级上的差别。可能的原因:这台机器的 SSD 本身读取足够快(和上一篇《Kafka 为什么写磁盘却还能很快》里测到的现象一致——这台机器上顺序访问模式下磁盘本身就不算慢),即使真的发生了缓存驱逐,从磁盘重新读入的代价也没有传统印象里那么大。这次没能测出一个足够大的"冷读"与"热读"数字差异,如实记录这个边界,而不是调整测试参数硬凑一个更有戏剧性的结果。

结论

这次做的两组实验,第一组(Broker 进程重启,读取速度基本不受影响)是支持"Kafka 依赖操作系统 page cache 而非自己管理缓存"这个结论的直接证据;第二组(尝试用内存压力驱逐缓存)没有测出显著差异,如实报告为一次的、有价值的空结果——这台 SSD 本身的读取性能足够高,削弱了"冷读明显慢"这个前提本身,也间接说明为什么在现代硬件上,"要不要自己管理缓存"这个设计选择的收益,可能更多来自于"少一份内存拷贝、少一份 GC 压力、代码更简单",而不是单纯的"避免磁盘 I/O 有多慢"。

分步演示:进程死了又重启,那份"缓存"还在不在

每一步都是本文上两节kill -9重启Broker、制造内存压力得到的原始结果。

炸掉整个Broker进程重启,读取速度基本没受影响
这一步新验证的结果

    参考与说明

    • 本文数据来自的 Kafka 4.3.1 Broker:用的 Java Consumer(跳过 console consumer 工具本身的固定开销)精确测量消费耗时, kill -9 并用相同参数重新启动 Broker 进程,用 dd 写入/读取 8GB 数据制造内存压力。
    • 和《Kafka 为什么写磁盘却还能很快》一起看:那篇测出这台机器的 SSD 上"不强制 fsync"比"顺序写本身"更能解释写入速度快,这篇的"冷读没测出明显变慢"某种程度上呼应了同一个硬件层面的事实——这台机器的 SSD 本身就快,削弱了很多传统"磁盘慢所以要靠缓存"的经典论证在这台具体机器上的说服力。
    • 没有涉及:本文没有用更精确的工具(比如读取 /proc/vm_stat 中和这个具体文件相关的缓存页统计,或者用 dtrace/fs_usage 追踪这次读取到底有没有触发磁盘 I/O)来直接证明"是否命中了 page cache",这次只能从进程重启前后的速度对比间接推断,没有拿到操作系统层面"这个文件的哪些页在缓存里"的直接证据,留给以后有权限做更底层观测的环境。
    京ICP备2026067502号-1京公网安备11010502063487号