顺序写、零拷贝与消费进度管理
Kafka 系列 · 第 10/11 篇
下一篇预告:《Kafka 功能扩展——压测、监控、KRaft 与流式》
开头:面试常问的三板斧
「Kafka 为什么快?」——文件结构、顺序写、零拷贝、刷盘策略,再加上上一篇的索引与分段。本篇把 Broker 端 IO 路径讲透;消费进度在上一篇已介绍 __consumer_offsets,这里补充与高效读写的衔接。
一、文件结构如何加速读

- 多 Partition → 多文件并行读,提高 Topic 级吞吐。
- 稀疏 index → 减少全文件扫描。
- 单 log 段 1GB → 控制 mmap 映射大小(一般不建议映射超过 2GB 文件)。
二、顺序写磁盘

Kafka 创建 log 段时预占连续磁盘空间,写入只在文件末尾追加(顺序写)。随机写要先找空闲块、可能碎片化,HDD 上差距极大——官方测试顺序写可达 ~600MB/s,接近内存写;随机写仅 ~100KB/s 量级。
这是 Kafka 选 日志型 append-only 的核心原因之一。
三、零拷贝
传统读文件发网络:磁盘 → 内核缓冲区 → 用户缓冲区 → Socket 缓冲区 → 网卡,多次拷贝。

Linux 零拷贝两种常见方式:
1. mmap(内存映射)
用户态持有文件映射,「遥控」内核读写,减少用户态与内核态拷贝。Java 中 DirectByteBuffer 等会用到。Kafka 把 log 段控制在 1GB 内,便于 mmap 加速写盘。
2. sendfile
用户态发 sendfile 指令,数据不经过用户空间,直接从页缓存拷到 Socket。Broker poll 消息给 Consumer 时:读本地 log → 网卡,只需 sendfile,用户态不解析内容。

JDK FileChannel.transferTo/transferFrom 底层即 sendfile。操作系统提供能力,上层语言只是封装。
四、刷盘频率与 PageCache
应用写的文件先进入内核 PageCache,由 OS 决定何时 fsync 落盘。Kafka 默认不每条消息 fsync,而靠:
| 参数 | 含义 |
|---|---|
log.flush.interval.messages | 累积条数触发刷盘 |
log.flush.interval.ms | 消息在内存保留时长 |
log.flush.scheduler.interval.ms | 刷盘检查频率 |
默认值极大,相当于交给 OS 后台 flush——性能优先。异常断电可能丢 PageCache 中未刷盘数据;这是所有依赖 PageCache 的应用共同面临的问题,Kafka 没有「来一条 fsync 一条」的同步刷盘模式。
RabbitMQ 也强调服务端无法 100% 保证不丢,需 Publisher Confirms;RocketMQ 提供同步刷盘选项(代价是吞吐)。业务上只能在性能 vs durability 间权衡。
五、消费进度再回顾
高效读 log 的同时,Consumer 通过 commit 更新 __consumer_offsets(见第 9 篇)。Offset 与 HW 不同:HW 管副本一致性边界,Offset 管消费组读到哪里。
手动改 Offset、从指定位置消费,都会反映到系统 Topic;与 auto.offset.reset、enable.auto.commit 配合理解完整消费语义。
小结
| 手段 | 作用 |
|---|---|
| 分段 + 稀疏索引 | 快速定位、并行读 |
| 顺序写 | 逼近磁盘顺序带宽 |
| mmap / sendfile | 减少拷贝,Broker→Consumer 路径关键 |
| 默认异步刷盘 | 换吞吐,断电有风险 |
下一篇:压测、EFAK 监控、KRaft、Kafka Streams 等扩展能力。