Kafka 为什么能做到这么高的吞吐量?
简化版
Kafka 高吞吐靠这几个关键设计:① 磁盘顺序写——消息顺序追加到日志文件,顺序写磁盘的速度接近甚至超过随机写内存,避免了随机 I/O 的寻址开销;② 零拷贝(Zero-Copy)——用 sendfile 让数据从磁盘直接经内核发到网卡,跳过用户态拷贝,消费时省掉多次数据复制;③ 分区并行——一个 Topic 分多个分区,多台 Broker、多消费者并行读写;④ 批量 + 压缩——生产者攒一批消息再发、并压缩,减少网络请求次数和传输量;⑤ Page Cache——读写都走操作系统页缓存,很多读请求直接命中内存。
详细版
五大高吞吐设计:
| 设计 | 原理 | 效果 |
|---|---|---|
| 顺序写磁盘 | 消息只追加(append)到分区日志末尾 | 顺序 I/O 无寻址,速度极快 |
| 零拷贝 | sendfile 磁盘→内核→网卡,跳过用户态 | 消费时省去多次拷贝和上下文切换 |
| 分区并行 | Topic 分多 Partition,分散到多 Broker | 读写并行,水平扩展 |
| 批量 + 压缩 | 生产者攒批发送、批量压缩(gzip/snappy/lz4) | 减少网络请求数和传输量 |
| Page Cache | 读写走操作系统页缓存 | 读多命中内存,写先入缓存异步刷盘 |
完整版教学
一、顺序写磁盘:颠覆”磁盘慢”的直觉
很多人以为「Kafka 快是因为用内存」,其实 Kafka 的消息是持久化到磁盘的,但依然快——关键在于它用的是顺序写(append-only)。
Kafka 每个分区是一个只追加的日志文件(commit log),新消息永远追加到文件末尾,从不修改、不随机插入。而磁盘的性能瓶颈主要是随机 I/O 的磁盘寻址(磁头移动/寻道)——随机写要不停寻址,极慢;而顺序写不用寻址,磁头一直往后写,速度惊人:顺序写磁盘可达几百 MB/s,甚至超过随机写内存。
所以 Kafka「利用磁盘顺序写」这个特性,既获得了持久化(数据不丢),又拿到了接近内存的写入速度。这是它高吞吐的地基。
二、零拷贝:消费时省掉多次数据搬运
消费者从 Kafka 拉消息,本质是「把磁盘上的日志文件数据发到网络」。传统方式要经过 4 次拷贝、2 次上下文切换:
- 磁盘 → 内核缓冲区(DMA 拷贝);
- 内核缓冲区 → 用户态应用缓冲区(CPU 拷贝);
- 用户态 → 内核 socket 缓冲区(CPU 拷贝);
- socket 缓冲区 → 网卡(DMA 拷贝)。
中间两次「内核↔用户态」的拷贝纯属浪费——数据只是路过应用、没做任何处理。
零拷贝(Zero-Copy) 用操作系统的 sendfile 系统调用,让数据从磁盘(内核)直接送到网卡(内核 socket),跳过用户态的两次拷贝和上下文切换。数据不再经过应用进程的内存。这大幅减少了 CPU 消耗和内存带宽占用,消费吞吐显著提升。
三、分区并行:水平扩展的基础
一个 Topic 被分成多个分区(Partition),分区可以分布在**不同的 Broker(服务器)**上。这带来并行:
- 生产并行:不同分区可以同时被写入。
- 消费并行:一个消费者组里,不同消费者消费不同分区,并行消费。
- 水平扩展:加机器、加分区就能线性提升整体吞吐。
分区是 Kafka 并行和扩展的基本单位——单机顺序写已经很快,再乘以多分区多机并行,总吞吐就非常高。
四、批量 + 压缩:减少网络开销
网络请求是有固定开销的(每次请求都有握手、协议头等成本)。如果一条消息发一次请求,海量小请求的开销会成为瓶颈。Kafka 的优化:
- 批量发送:生产者不是来一条发一条,而是攒一批(
batch.size或等linger.ms时间)再一次性发送。用少量大请求代替大量小请求,摊薄网络开销。 - 压缩:批量的消息可以整体压缩(gzip、snappy、lz4、zstd)后再传输,减少网络带宽占用。压缩在生产者端做、消费者端解压,Broker 存的也是压缩数据(省磁盘)。批量越大,压缩率越好。
批量 + 压缩显著提升了单位网络带宽能传输的消息数。
五、Page Cache:让操作系统帮忙缓存
Kafka 不自己在 JVM 堆里维护消息缓存,而是充分利用操作系统的 Page Cache(页缓存):
- 写入:消息写入其实是写到 Page Cache(内存),由操作系统异步刷盘——写入方感觉是「写内存」,很快。
- 读取:读消息时,如果数据还在 Page Cache 里(刚写入不久的热数据),直接从内存读,不用访问磁盘。而消费者往往消费的是刚生产的新消息,命中率很高。
好处:
- 避免 JVM GC:缓存交给操作系统管理,不占 JVM 堆,没有大堆 GC 问题。
- 重启不丢缓存:Page Cache 由 OS 管理,Kafka 进程重启后缓存还在。
- 读写都受益:写走缓存快,读多命中缓存快。
六、常见误区与追问
| 考点 | 正确口径 |
|---|---|
| 顺序写 | 追加写日志,减少随机 IO |
| Page Cache | 利用 OS 缓存和批量刷盘 |
| 批处理/压缩/零拷贝 | 减少网络包、磁盘写和 CPU 拷贝 |
producer batch 1000 records
-> append to log segment sequentially
-> page cache
-> consumer fetch
-> sendfile / zero-copy path
Kafka 快不是单点技巧,而是顺序 IO、批处理、分区并行和零拷贝组合起来的结果。
- 误区:Kafka 快是因为全部放内存。 Kafka 主要依赖顺序写磁盘和 Page Cache,不是把所有消息都存在 JVM 内存。
- 误区:批量越大越好。 批量大提升吞吐但增加延迟和内存占用,要按业务调 batch.size 和 linger.ms。
- 误区:分区越多吞吐无限增加。 分区过多会增加文件句柄、复制、选举和调度成本。
- 追问:顺序写为什么快? 磁盘顺序写避免大量寻道,SSD 上也能减少写放大和元数据开销。
- 追问:零拷贝在 Kafka 哪体现? 消费者拉取日志时可利用 sendfile 从页缓存发送到 socket,减少用户态拷贝。
- 追问:压缩为什么可能提升吞吐? 压缩减少网络和磁盘字节数,代价是 CPU 消耗。
七、加强记忆
Kafka 高吞吐靠五大设计:① 磁盘顺序写——消息只追加到分区日志末尾,顺序 I/O 无寻址开销,速度接近甚至超过随机写内存(既持久化又快);② 零拷贝——用 sendfile 让数据从磁盘内核直达网卡,跳过用户态两次拷贝和上下文切换,消费更快;③ 分区并行——Topic 分多 Partition 分散到多 Broker,读写并行、水平扩展;④ 批量 + 压缩——生产者攒批发送并压缩,减少网络请求数和传输量;⑤ Page Cache——读写走操作系统页缓存,避免 JVM GC、读多命中内存。记忆锚点:顺序写、零拷贝、分区并行、批量压缩、页缓存——磁盘也能飞快。