4. Broker 核心参数调优:网络与 IO 线程模型

Kafka 的 Broker 本质上是一个网络服务程序。它要处理海量的客户端请求,还要把数据刷到磁盘。这两个任务如果混在一起干,系统很容易卡死。所以 Kafka 设计了一套线程模型,把网络接收和磁盘读写分开。

这套模型的核心参数有五个:num.network.threads、num.io.threads、socket.send.buffer.bytes、socket.receive.buffer.bytes、queued.max.requests。调好它们,Broker 的性能能翻倍。调不好,CPU 和内存都在浪费。

核心观点:网络线程负责“接活”,IO 线程负责“干活”。两者比例失调,就是性能瓶颈的开始。

4.1 线程模型全景图

先看一张图,理解数据是怎么流转的。我个人习惯在调优前,先把架构图画出来,这样心里有底。

Kafka Broker 网络与 IO 线程模型 生产者客户端 消费者客户端 网络线程池 num.network.threads 接收请求 解析协议 放入请求队列 请求队列 queued.max.requests 背压缓冲区 响应队列 待发送响应 IO 线程池 num.io.threads 磁盘读写 消息追加 偏移提交 磁盘 /data/kafka 网络线程只做轻量级协议解析,绝不碰磁盘。IO 线程只做磁盘读写,绝不阻塞网络。 请求队列是两者的解耦缓冲区,防止网络突发流量压垮磁盘。

这张图我画过很多次。每次给团队做培训,我都会指着请求队列说:这是整个模型的灵魂。没有它,网络线程和 IO 线程就是紧耦合,一卡全卡。

4.2 num.network.threads:网络线程数

这个参数控制 Broker 用于接收客户端请求的线程数量。说白了,就是前台接待员的人数。

参数名 默认值 建议范围
num.network.threads 3 CPU 核数 × 2 到 × 3

调优原则:

  • 网络线程是 CPU 密集型任务,主要做序列化/反序列化、协议解析、权限校验。
  • 不要设置太多。线程多了,上下文切换开销反而吃掉性能。
  • 我一般建议:num.network.threads = CPU核数 × 2。比如 16 核机器,设 32。

我的经验:有一次在 32 核机器上,我把这个值设成了 128。结果 CPU 大量花在线程切换上,吞吐反而下降了 15%。后来压测发现,32 到 48 之间是最佳点。别贪多。

4.3 num.io.threads:IO 线程数

IO 线程负责真正的“脏活累活”——写磁盘、读磁盘、管理偏移量。这是 Broker 最宝贵的资源。

参数名 默认值 建议范围
num.io.threads 8 CPU 核数 × 1 到 × 2

调优原则:

  • IO 线程通常处理磁盘操作,会有等待时间(磁盘寻道、页缓存命中)。
  • 如果磁盘是 SSD,IO 等待时间短,线程数可以少一些。
  • 如果是机械盘,IO 等待时间长,线程数可以多一些,让 CPU 在等待时干别的活。
  • 我个人习惯:SSD 场景用 num.io.threads = CPU核数,机械盘场景用 CPU核数 × 1.5。

注意:IO 线程数不要超过磁盘数量太多。比如你只有 2 块数据盘,却设了 32 个 IO 线程,磁盘会成为瓶颈,线程都在排队等 IO。我曾经见过一个案例,IO 线程设了 64,磁盘利用率只有 40%,因为线程全在锁竞争上。

4.4 网络缓冲区:socket.send.buffer.bytes 与 socket.receive.buffer.bytes

这两个参数控制 Broker 端网络连接的 TCP 缓冲区大小。很多人忽略它们,但它们在跨机房场景下特别重要。

参数名 默认值 建议范围
socket.send.buffer.bytes 102400 (100KB) 1MB - 4MB
socket.receive.buffer.bytes 102400 (100KB) 1MB - 4MB

调优原则:

  • 缓冲区越大,单次网络传输能打包的数据越多,吞吐越高。
  • 但缓冲区太大,会占用内存。每个连接都有独立的缓冲区。
  • 同机房低延迟场景,默认值 100KB 够用。跨机房高延迟场景,建议调到 1MB 以上。

关键点:这两个参数设置的是 Broker 端的 Socket 缓冲区。客户端也有对应的参数(send.buffer.bytes 和 receive.buffer.bytes),两边要配合调。我曾经遇到一个问题:Broker 端设了 2MB,客户端还是 100KB,结果吞吐上不去。两边都调到 2MB 后,问题解决。

4.5 queued.max.requests:请求队列深度

这个参数控制网络线程和 IO 线程之间的请求队列能积压多少请求。它是整个线程模型的“缓冲池”。

参数名 默认值 建议范围
queued.max.requests 500 500 - 2000

调优原则:

  • 队列太短:网络线程处理得快,IO 线程处理得慢,网络线程会被阻塞,客户端感受到延迟抖动。
  • 队列太长:积压请求太多,内存占用高,而且一旦 Broker 宕机,丢失的请求也多。
  • 我一般建议:先保持默认 500。如果监控发现网络线程经常等待(等待时间 > 1ms),可以逐步增加到 1000 或 1500。

避坑指南:我曾经把 queued.max.requests 调到 5000,以为能扛住更大的突发流量。结果内存暴涨,GC 频繁,吞吐反而下降。后来压测发现,1500 左右是最佳点。记住:队列不是越大越好,够用就行。

4.6 参数组合调优实战

这几个参数不是孤立的。它们互相影响。我总结了一个调优步骤,你可以照着做:

  1. 先确定机器规格:CPU 核数、内存大小、磁盘类型(SSD/HDD)。
  2. 计算基础值:
    • num.network.threads = CPU核数 × 2
    • num.io.threads = CPU核数(SSD)或 CPU核数 × 1.5(HDD)
  3. 设置缓冲区:
    • 同机房:保持默认 100KB
    • 跨机房:设为 1MB - 2MB
  4. 队列深度:先保持 500,压测后根据等待时间调整。
  5. 压测验证:用 kafka-producer-perf-test.sh 和 kafka-consumer-perf-test.sh 跑负载,观察吞吐和延迟。

下面是一个典型的配置示例:

# 16核CPU,SSD磁盘,同机房场景
num.network.threads=32
num.io.threads=16
socket.send.buffer.bytes=1048576
socket.receive.buffer.bytes=1048576
queued.max.requests=1000

总结:调优的核心是找到网络线程和 IO 线程的平衡点。网络线程太快,IO 线程跟不上,队列积压。IO 线程太快,网络线程来不及接收,CPU 空闲。用监控工具(如 JMX、Prometheus)观察线程等待时间和队列深度,比凭感觉调参靠谱得多。