这是《Kafka 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 调优的第一原则:没有测量就没有调优。本章给出一套从压测定位、分层调参到容量规划的完整方法。
17.1 先建立基线
任何改动前,先回答三个问题:
- 当前吞吐是多少(条/秒、MB/秒)?
- 端到端延迟是多少(P99)?
- 瓶颈在哪:生产者、网络、Broker 磁盘、还是消费者?
Producer -> 网络 -> Broker(磁盘/页缓存/副本) -> 网络 -> Consumer -> 下游
逐段测量,找出最长的那块板,避免“凭感觉调参”。
17.2 官方压测工具
生产者压测
bin/kafka-producer-perf-test.sh \
--topic perf-test \
--num-records 1000000 \
--record-size 1024 \
--throughput -1 \
--producer-props bootstrap.servers=localhost:9092 \
acks=all linger.ms=10 compression.type=zstd
输出关注:records/sec、MB/sec、avg latency、max latency、percentile。
消费者压测
bin/kafka-consumer-perf-test.sh \
--bootstrap-server localhost:9092 \
--topic perf-test \
--messages 1000000 \
--reporting-interval 1000
压测建议:
- 用与生产一致的消息大小与压缩算法;
- 分组测试不同
linger.ms、batch.size、线程数,每次只改一个变量; - 观察服务端
BytesInPerSec/BytesOutPerSec,确认瓶颈不在客户端; - 压测 topic 的分区数覆盖多台 Broker,避免单盘打满掩盖集群能力。
17.3 操作系统层
| 项 | 建议 |
|---|---|
| 文件句柄 | ulimit -n 100000+,分区多时按 分区数*3 估算 |
| swappiness | vm.swappiness=1,尽量避免页缓存被换出 |
| 文件系统 | XFS(首选)或 ext4,挂载 noatime |
| 磁盘 | SSD/NVMe,多盘条带化 log.dirs;避免与 OS、日志混盘 |
| 网络 | 万兆起步,txqueuelen、缓冲区按需调整 |
| 时间同步 | NTP/chrony,时间戳语义依赖时钟 |
Kafka 是少数“吃页缓存比吃 JVM 堆更划算”的服务:
机器 64GB 内存:
Broker JVM 堆 6-8GB(足够)
剩余 50GB+ 留给页缓存(关键!)
17.4 Broker 调优
| 参数 | 默认 | 建议 |
|---|---|---|
num.network.threads |
3 | 看 NetworkProcessorAvgIdlePercent,低于 30% 加到 6-8 |
num.io.threads |
8 | 看 RequestHandlerAvgIdlePercent,磁盘快时适当加大 |
num.replica.fetchers |
1 | ISR 频繁收缩/写入大时调到 2-4 |
num.partitions |
1 | 新 topic 默认分区数,显式规划覆盖 |
log.flush.interval.messages |
MAX | 保持默认,依赖 OS |
socket.send/receive.buffer.bytes |
100KB | 大流量可调大到 1MB |
replica.fetch.max.bytes |
1MB | 与生产端大消息匹配 |
message.max.bytes |
~1MB | 大消息需同步调客户端 |
核心指标驱动:
NetworkProcessorAvgIdlePercent < 0.3 -> 加网络线程
RequestHandlerAvgIdlePercent < 0.3 -> 加 IO 线程
UnderReplicatedPartitions > 0 持续 -> 副本追赶能力不足
RequestLatencyAvg 高 -> 磁盘/页缓存/请求堆积
17.5 生产者调优
高吞吐模板:
linger.ms=20
batch.size=65536
compression.type=zstd
buffer.memory=67108864
acks=1 # 若业务允许;可靠场景保持 all
低延迟模板:
linger.ms=0
acks=1
compression.type=lz4
enable.idempotence=true
取舍关系:
linger.ms调大 -> 批更大 -> 吞吐升、延迟升;compression=zstd-> CPU 升、网络/磁盘降,通常净赚;acks=0/1/all-> 吞吐降、可靠性升;- 大消息会破坏批次效率与页缓存命中率,能拆小就拆小,大内容放对象存储、消息只放引用。
17.6 消费者调优
fetch.min.bytes=1
fetch.max.wait.ms=500
max.partition.fetch.bytes=2097152
max.poll.records=500
吞吐优先时:
- 增加消费者实例直到 = 分区数;
- 批量处理(攒批写库);
- 调大
fetch.min.bytes(如 1KB)减少小包; - 下游异步化/线程池化,但要自己管理位移。
延迟优先时:fetch.min.bytes=1、小 max.poll.records、处理逻辑轻量化。
17.7 JVM 调优
推荐起点:
-Xms6g -Xmx6g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=20
-XX:InitiatingHeapOccupancyPercent=35
要点:
- 堆不要盲目开大:6-8GB 通常足够,剩余内存留给页缓存;
- GC 停顿会同时影响心跳与 poll(rebalance 风暴诱因);
- 堆外内存也要算:网络缓冲、压缩缓冲在 native memory,容器里
memory limit要留余量。
17.8 容量规划
磁盘
日数据量 = 峰值MB/s * 86400 (按均值更准)
保留7天 * 副本3 * 压缩比(约0.7) * 1.2(索引/元数据)
例:均值 100MB/s -> 100*86400=8.64TB/天(单副本)
保留7天、RF=3 -> 8.64*7*3*1.2 ≈ 217TB
网络
入流量 = 写入
出流量 = 写入 * 消费副本数(消费者数) + 副本同步
例:写入 1Gbps、3个独立消费组、RF=3
Broker 间副本 ≈ 2x 写入,出向消费 ≈ 3x 写入
万网卡可能接近打满,需评估
分区与机器
- 单分区吞吐经验值:写约 10-30MB/s(取决于消息大小/盘/acks);
- 目标吞吐 / 单分区吞吐 = 最少分区数,再留 30-50% 余量;
- Broker 数量由磁盘容量 + 网络吞吐 + 副本分布共同决定。
17.9 一个调优案例
现象:生产端 P99 从 50ms 涨到 2s,吞吐只有 3 万条/s。
排查:
- Broker
RequestHandlerAvgIdlePercent=0.9(不忙); - 磁盘 util 95%(写入瓶颈);
- 消息未压缩、
linger.ms=0,请求碎片化严重。
动作:开启 zstd、linger.ms=15、batch.size=64KB;两块新盘加入 log.dirs。
结果:磁盘 util 降到 65%,吞吐 12 万条/s,P99 80ms。
这个案例说明:调优常常不是“加线程”,而是减少无效 IO 与请求碎片化。
本章小结
- 先测量再调优:压测工具 + 服务端指标定位瓶颈段;
- 页缓存是 Kafka 性能核心,JVM 堆不要贪大;
- 线程参数由 idle 指标驱动,盲目加线程无用;
- 生产端吞吐靠攒批与压缩,消费端吞吐靠并行与批量处理;
- 容量规划要同时算磁盘、网络、分区与副本。
思考题
- 为什么“给 Kafka 开 32GB 大堆”通常反而会伤害性能?
linger.ms从 0 调到 20ms,为什么吞吐和延迟可能同时变得更可控?- 规划一个日均 10TB、保留 3 天、RF=3 的集群,需要多少裸容量?