Page Cache 与零拷贝
定义与作用
Kafka 高性能的底层秘密在于两个操作系统特性:Page Cache(页缓存) 和 Zero Copy(零拷贝)。Kafka 不使用 JVM 堆缓存数据,而是将数据直接写入 OS 的 Page Cache 中,利用 sendfile 系统调用实现从磁盘到网络的高效数据传输,避免了不必要的内存拷贝。
核心原理
传统数据路径 vs Kafka 路径
链路对比:
| 传统 | 零拷贝(sendfile) | |
|---|---|---|
| DMA Copy | 2 | 2 |
| CPU Copy | 2 | 0 |
| 上下文切换 | 4 | 2 |
| JVM 堆参与 | 是(GC 压力) | 否 |
Page Cache 工作原理
Page Cache 的好处:
- 热数据(最近写入/频繁读取)保留在内存中,读命中率高
- 不占 JVM 堆内存,不受 GC 影响
- 依赖 OS 管理,Kafka 代码简化
零拷贝的实际效果
场景:1GB 消息发送给 Consumer,JVM 堆 6GB
传统方式:
- 需要将 1GB 数据读到 JVM 堆 → 触发 Full GC
- 再从 JVM 堆拷贝到 Socket → 额外的 CPU 消耗
- 内存带宽消耗:2GB(读+写)
零拷贝:
- 数据不走 JVM 堆,直接 Page Cache → Socket
- 不会触发 GC
- 内存带宽消耗:仅页表映射开销
完整示例
示例一:验证 Page Cache 对读写的影响
场景:写入 1GB 数据后,测试 Consumer 首轮读取(冷读)和再次读取(热读)的差异。
# 1. 写满 Page Cache
bin/kafka-producer-perf-test.sh --topic cache-test --num-records 10000000 \
--record-size 100 --throughput -1 --producer-props bootstrap.servers=localhost:9092
# 2. 清空 Page Cache(需要 root)
echo 3 | sudo tee /proc/sys/vm/drop_caches
# 3. 冷读:Consumer 从磁盘读
bin/kafka-consumer-perf-test.sh --topic cache-test --messages 10000000 \
--bootstrap-server localhost:9092
# start.time, end.time, data.consumed.in.MB, MB.sec
# 2026-06-13 ..., ..., 953.674, 120.5 (冷读速度)
# 4. 热读:Consumer 从 Page Cache 读(不清理缓存)
bin/kafka-consumer-perf-test.sh --topic cache-test --messages 10000000 \
--bootstrap-server localhost:9092
# 2026-06-13 ..., ..., 953.674, 850.3 (热读速度 ~7x)
示例二:监控 Page Cache 使用
# 查看 Kafka 数据目录的 Page Cache 占用
# Linux
vmtouch /data/kafka/topic-orders-0/
# Files: 12
# Directories: 1
# Resident Pages: 262144/524288 4G/8G 50%
# Elapsed: 0.012 seconds
# 或使用 fincore
fincore /data/kafka/topic-orders-0/00000000000000000000.log
# Windows
# 查看 Standby Cache / Modified Page List (PerfMon)
易错场景
易错 1:给 JVM 堆分配太多内存,排挤 Page Cache
场景:32GB 物理内存,JVM 堆 -Xmx28G。
后果:只剩 4GB 给 Page Cache。Kafka 大量数据需要从磁盘读,性能严重下降。
最佳实践:
- JVM 堆:6GB-8GB(Kafka 对堆内存需求不大,主要是元数据和网络缓冲)
- 剩余内存给 OS Page Cache:24GB-26GB
- 遵循 60-70% 物理内存归 OS 的原则
易错 2:禁用 Swap 的副作用
场景:vm.swappiness=0 + 内存不够时 OOM Killer 直接杀 Kafka。
正确做法:
# /etc/sysctl.conf
vm.swappiness=1 # 极少使用 swap,但不完全禁用
vm.dirty_ratio=10 # 脏页占内存 10% 即开始写回
vm.dirty_background_ratio=5
面试高频考点
Q:Kafka 为什么不自己管理缓存(像数据库那样),而是依赖 OS Page Cache?
A:
- 避免双重缓存:如果 Kafka 自己做内存缓存 + OS 也有 Page Cache → 数据被缓存两次 → 浪费内存
- 利用顺序 I/O 的特性:Kafka 的读写模式高度顺序化,与 Page Cache 的预读(read-ahead)机制天然契合
- 简化堆管理:Kafka 是 JVM 应用,大量数据放堆会导致 GC 暂停。Page Cache 不占堆内存
- OS 比自己更懂磁盘:Page Cache 的淘汰策略(LRU)、脏页写回时机等由 OS 根据全局内存压力优化,比应用层"盲猜"更优
代价:跨机房/副本同步时,如果数据量超过 Page Cache,性能会急剧下降("缓存悬崖"效应)。