Kafka 为什么这么快?

#中间件#面试八股

Kafka 为什么这么快?

结论:Kafka 的快不是单点优化,而是六个设计叠在一起——分区顺序追加写、操作系统页缓存(Page Cache)、零拷贝(Zero-Copy)发送、批量发送与压缩、分区级并行、稀疏索引定位。它把"随机写 + 多次内存拷贝 + 逐条网络往返"这三类开销同时消掉了。

一、六项优化

优化做法省掉了什么
顺序写每个分区只追加到日志段末尾磁盘随机寻道,顺序写可接近磁盘带宽上限
页缓存消息不存 JVM 堆,读写都走 OS Page Cache对象开销与 GC 停顿,热数据直接命中内存
零拷贝sendfile 把页缓存直接送到网卡缓冲区内核态与用户态之间的两次数据拷贝
批量 + 压缩按 batch.size / linger.ms 攒批,整批压缩每条消息的协议头与网络往返
分区并行一个 Topic 拆多个分区,分散到多台 Broker单机磁盘与网卡的瓶颈
稀疏索引.index 每 4KB 记一条,二分后顺序扫全量索引的内存占用

二、零拷贝到底省了什么

传统 read + write(4 次拷贝、4 次上下文切换)
磁盘 ──DMA──> 内核页缓存 ──CPU──> 用户缓冲区 ──CPU──> Socket 缓冲区 ──DMA──> 网卡

sendfile(2 次拷贝、2 次上下文切换)
磁盘 ──DMA──> 内核页缓存 ──────────DMA──> 网卡
                     (仅传递描述符,不经过用户态)

Kafka 消费端读消息时,数据本来就在页缓存里,sendfile 让内核直接把页缓存内容交给网卡,应用层完全不碰消息体。这也是为什么 Kafka 的堆内存可以设得很小(6~8GB 足够),而机器内存越大缓存命中率越高。

注意:启用 TLS 后 sendfile 无法直接使用(需要用户态加密),零拷贝收益会打折扣,这是"上 TLS 后吞吐下降"的主要原因之一。

三、生产端的关键参数

# 攒批:batch.size 与 linger.ms 共同决定"多大、等多久"
batch.size=131072            # 128KB 一批
linger.ms=10                 # 最多等 10ms,用延迟换吞吐
buffer.memory=67108864       # 发送缓冲区 64MB,满了会阻塞 send
compression.type=lz4         # 压缩算法,lz4 兼顾 CPU 与压缩比
acks=all
enable.idempotence=true

调优顺序建议:先确认 buffer.memory 够用(否则 send 阻塞或抛异常),再调 batch.size 与 linger.ms,最后选压缩算法。盲目把 linger.ms 调到 100ms 会让端到端延迟明显变差。

四、Broker 侧与硬件

num.io.threads=16            # 磁盘 IO 线程,建议不小于磁盘数
num.network.threads=8        # 网络线程
log.segment.bytes=1073741824 # 单日志段 1GB,切段后才可被删除
log.flush.interval.messages=10000

实践中的三条经验:

  1. 磁盘用顺序写友好的介质,RAID10 或本地 NVMe 都行,但不要把日志目录和系统盘混用;
  2. JVM 堆不要过大,堆越大 Full GC 越久,页缓存反而变少;
  3. 文件系统预留空间(log.retention 与磁盘水位),写满会直接导致 Broker 不可写。

五、消费端为什么也快

消费侧的吞吐同样靠"批量 + 零拷贝":

参数默认值调优方向
fetch.min.bytes1调到 1MB 左右,避免没数据也频繁往返
fetch.max.bytes50MB大消息场景适当调大
max.partition.fetch.bytes1MB必须大于单条最大消息,否则永远拉不动
max.poll.records500按单条处理耗时下调,防止处理超时

Broker 不主动推送,只在消费者请求时按批返回,并把位点维护交给客户端。这样 Broker 侧几乎无状态,扩容与故障恢复都更简单。

六、常见追问

  • 顺序写为什么比随机写快这么多? 机械盘随机写每次都要寻道,顺序写几乎无寻道;即使是 SSD,顺序写也能减少写放大与 GC 压力。
  • 零拷贝是不是就不经过内核了? 仍经过内核,只是不再把数据复制到用户态;DMA 负责磁盘到内存、内存到网卡的搬运。
  • 批量发送会不会丢消息? 不会,但会增加延迟;linger.ms=0 时批很小,吞吐上不去,属于用延迟换吞吐的典型取舍。
  • 分区越多越快吗? 到一定程度后反而变慢:分区多意味着更多文件句柄、更多副本同步任务,Controller 故障恢复也更慢。

小结

  • Kafka 快的本质是"顺序 IO + 页缓存 + 零拷贝 + 批量",每一层都针对一类开销。
  • 零拷贝依赖数据留在页缓存,堆内缓存反而会拖慢并增加 GC。
  • 吞吐调优优先看攒批参数与压缩,其次才是分区与线程数。
  • 分区数不是越多越好,超过消费者与磁盘的承载能力后收益为负。
笔记加载中…