上一篇讲完 Kafka 的定位和消息语义,本篇进入最硬核的部分——Kafka 凭什么能扛十万级 TPS。这一篇是面试高频题,也是 Kafka 设计哲学的精华所在。
一、吞吐量大 = 消息一入一出快
吞吐量大,本质就是消息的一入一出更快。Kafka 消息的一入一出,都做了哪些操作?
- 读取消息内容
- 将消息持久化到磁盘
- 消费者从磁盘读取消息消费
注意中间这一步——Kafka 是要把消息写到磁盘的。按直觉,写磁盘怎么会比 RabbitMQ 的纯内存快?答案就藏在「怎么写磁盘」上。
二、四大吞吐量支柱
| 支柱 | 关键技术 |
|---|---|
| 顺序读写 | 日志追加 + 磁盘顺序 IO |
| 并行 | partition 机制 |
| 批量 | 批量发送 / 接收 + 数据压缩 |
| 零拷贝 | sendfile(transferTo) |
下面逐个拆。
三、日志顺序读写与快速检索
3.1 Kafka 日志的物理结构
Kafka 日志以 partition 为单位保存:
- 目录格式:
{Topic名称}-{数字}(如order-events-0、order-events-1) - 每条日志消息由 4 字节整型 + N 字节消息体组成
/order-events-0/
├── 000000000000000170410.index ← 索引文件
├── 000000000000000170410.log ← 数据文件
├── 000000000000000170420.index
└── 000000000000000170420.log
3.2 日志分段(Segment)
日志是分段的——每个 partition 日志会分成 N 个大小相等的 segment:
- partition 只支持顺序读写(磁盘顺序 IO 效率非常高)
- partition 把消息追加到最后一个 segment
- 当 segment 达到一定阈值后刷盘(flush)
- 刷盘后 consumer 才能读取消费
顺序写的恐怖效率:在 SSD 上,磁盘顺序写可达 几百 MB/s,几乎与内存随机写持平。Kafka 把所有写入都做成顺序追加,是它高吞吐的地基。
3.3 index + log 双文件 + 稀疏索引
每个 partition 日志文件由两个文件构成:index 文件和 log 数据文件。

index 文件存的是 offset → 物理位置(position)的映射:
| 相对 offset | position |
|---|---|
| 1 | 0 |
| 3 | 348 |
| 4 | 476 |
| 8 | 1325 |
| … | … |
注意是稀疏索引——并不是每条消息一条索引,而是每隔若干条消息记录一次。
3.4 检索流程
index 前半部分是 partition 的 offset,根据 offset 从 index 中获取日志存储的 position;position 主要包含「消息头 + 消息内容长度」;定位到 position 后直接去 log 文件,根据消息头长度读取一条完整消息。
完整流程:
- 二分查找 index:定位到目标 offset 所在的区间
- 从 position 开始顺序扫描 log:找到精确的目标消息
查 offset 170415:
index 二分 → 找到 (4, 476) 这一项最接近
从 log position 476 开始顺序扫
→ Message170414 (476)
→ Message170415 (789) ✅ 命中
为什么用稀疏索引而不是稠密索引? 稠密索引会让 index 文件和 log 一样大,内存扛不住。稀疏索引牺牲一点点查询时间(多扫几条),换来 index 可以常驻内存——这是 Kafka 在「空间 vs 时间」上的精妙取舍。
四、partition 并行
partition 是 Kafka 的并行单元:
- 一个 topic 拆成 N 个 partition
- 不同 partition 可以并行读写(分布在不同的 broker 上)
- consumer 数量 ≤ partition 数量时,消费端并行度 = consumer 数
Topic (6 partitions) Consumer Group (3 consumers)
┌──────────────┐ ┌──────────────┐
│ Partition 0 │ ─────────► │ Consumer 1 │ (消费 0, 1)
│ Partition 1 │ ─────────► │ │
├──────────────┤ ├──────────────┤
│ Partition 2 │ ─────────► │ Consumer 2 │ (消费 2, 3)
│ Partition 3 │ ─────────► │ │
├──────────────┤ ├──────────────┤
│ Partition 4 │ ─────────► │ Consumer 3 │ (消费 4, 5)
│ Partition 5 │ ─────────► │ │
└──────────────┘ └──────────────┘
partition 数 = 并行度上限。设计 topic 时要预估消费端需要的并发数——partition 设少了,加 consumer 也没用(多余的 consumer 闲置)。
五、批量发送 + 数据压缩
Kafka Producer 不是「来一条发一条」,而是攒一批一起发:
- 批量发送:producer 把消息攒到 batch 里,达到
batch.size或linger.ms触发发送 - 批量接收:consumer 一次拉一批(
fetch.min.bytes) - 数据压缩:整个 batch 可以用 snappy / lz4 / gzip / zstd 压缩
传统 MQ:1 msg = 1 次网络 IO
msg ──► ────────► broker
Kafka:batch = 1 次网络 IO
msg1 ┐
msg2 ├─ batch(压缩)──► broker
msg3 ┘
批量的双重红利:不仅网络 IO 次数减少,压缩后网络传输字节数也大幅下降。在大数据场景下,批量 + 压缩常常让 Kafka 的实际网络流量降一个数量级。
六、零拷贝(sendfile)
这是 Kafka 性能优化中最精彩的一笔。
6.1 标准文件读取的 4 次拷贝
正常情况下,Linux 的内核空间分用户进程和系统进程。Kafka 读取一个文件并发送到网络,标准方式要走 4 步:

- 磁盘 → 内核读取缓冲区(DMA 拷贝)
- 内核读取缓冲区 → 用户缓冲区(CPU 拷贝,进入应用上下文)
- 用户缓冲区 → socket 缓冲区(CPU 拷贝)
- socket 缓冲区 → 网卡接口(DMA 拷贝)
应用上下文 内核空间上下文
│ │
│ ┌────┴─────┐
│ │ 磁盘文件 │
│ └────┬─────┘
│ │ ① DMA
│ ┌────┴─────┐
│ │ 内核读缓冲│
│ └────┬─────┘
│ │ ② CPU
┌──────────────┴┐
│ 用户缓冲区 │
└──────────────┬┘
│ │ ③ CPU
│ ┌────┴─────┐
│ │ socket 缓冲│
│ └────┬─────┘
│ │ ④ DMA
│ ┌────┴─────┐
│ │ 网卡接口 │ ──► 消费者
│ └──────────┘
期间还需要 4 次上下文切换(用户态 ↔ 内核态)。这个流程经过多次数据拷贝,CPU 也参与了 2 次搬运。
6.2 sendfile 的 2 次拷贝
Kafka 没有使用标准的 JVM 文件读取,而是使用了 sendfile 的方式:

sendfile 提供了一个 transferTo() 函数:
- 支持磁盘文件直接读取到内核读取缓冲区
- 网络接口直接从内核读取缓冲区获取数据(不经过用户空间)
把上述 4 步缩减为 2 步:
步骤 ①:磁盘 → 内核读取缓冲区(DMA 拷贝)
步骤 ②:内核读取缓冲区 → 网卡接口(DMA 拷贝)
且避免了用户进程和系统内核的上下文切换(只剩 sendfile 调用与返回的 2 次切换)。
应用上下文 内核空间上下文
│ │
│ transferTo()│
│ ───────────► │
│ │
│ ┌────┴─────┐
│ │ 磁盘文件 │
│ └────┬─────┘
│ │ ① DMA
│ ┌────┴─────┐
│ │ 内核读缓冲│
│ └────┬─────┘
│ │ ② DMA(不经用户空间)
│ ┌────┴─────┐
│ │ 网卡接口 │ ──► 消费者
│ └──────────┘
│ ◄─────────── │ ack
6.3 为什么不用在 Windows 上
sendfile 是 Linux 系统实现的底层功能,Windows 系统并未实现——这也是不建议把 Kafka 服务部署到 Windows 上的根本原因。
现代视角(2026)补一句:现代 Linux(内核 4.x+)的 sendfile 配合 DMA gather copy,连「内核读缓冲区 → 网卡」这一步 CPU 拷贝都能省掉,只剩纯 DMA 传输——CPU 几乎不参与数据搬运。Kafka 的零拷贝红利在新内核上更明显。
七、四大支柱总结
| 优化 | 解决的瓶颈 | 节省的开销 |
|---|---|---|
| 顺序读写 | 磁盘随机 IO 慢 | 让磁盘 IO 接近内存 |
| partition 并行 | 单队列串行 | 并行度 = partition 数 |
| 批量 + 压缩 | 网络 IO 次数多 | 网络流量降一个数量级 |
| 零拷贝 sendfile | 内核↔用户空间拷贝 + 上下文切换 | 4 次拷贝 → 2 次,CPU 解放 |
这四条单独拿出来都不新鲜——日志结构、并行、批量、零拷贝都是 OS 课的老知识点。Kafka 的伟大在于把它们组合到一个系统里、用到极致。
八、和小白们的常见误解
| 误解 | 真相 |
|---|---|
| 「Kafka 用内存所以快」 | ❌ Kafka 写磁盘。快是因为顺序写 |
| 「partition 越多越好」 | ❌ partition 越多,broker 元数据压力越大、客户端内存越多 |
| 「零拷贝 = 完全没拷贝」 | ❌ 仍是 2 次拷贝,省的是用户↔内核之间的拷贝 |
| 「批量发送会拖慢延迟」 | ❌ 配合 linger.ms=0 可以做到「立即发」 |
partition 不是越多越好:每个 partition 都要在 broker 上有元数据、在客户端有缓冲区。几千 partition 的集群,broker 的元数据管理就开始吃力——这是 Kafka 早期 ZK 模式的痛点,也是 KRaft 模式要解决的问题。
九、小结
Kafka 高吞吐的设计哲学:用「顺序」换「随机」、用「并行」换「串行」、用「批量」换「单条」、用「内核态」换「用户态」。
| 章节 | 关键词 |
|---|---|
| 日志结构 | segment + index + log |
| 顺序读写 | 磁盘顺序 IO ≈ 内存 |
| 稀疏索引 | 二分 + 顺序扫描 |
| partition | 并行单元 |
| 批量 | 网络 IO 次数 ↓ |
| 零拷贝 | sendfile / transferTo |
下一篇「Kafka(下):消费者、有序性、命令与集群」收尾——讲清 groupid 怎么用、为什么消息会重复、本地怎么起 Kafka、怎么搭集群、设计一个 MQ 该考虑什么。
现代视角补一句(2026):四大支柱到 2026 年一条没变。新增的优化是 Partition Tiered Storage——热数据放本地磁盘,冷数据自动下沉到 S3/OSS。这让 Kafka 能堆 TB 级历史数据而不用每个 broker 都扛全量。底层原理还是这一套。