Kafka 的默认配置目标是「安全稳妥」,离「跑满硬件」还有很大距离。同样是 3 节点集群,调优前后的吞吐可以相差一个数量级。但调优不是乱拧参数——它受吞吐、延迟、持久化三者的权衡支配,且瓶颈通常不在 CPU 而在磁盘与网络。本文从性能模型出发,逐层讲透生产者、消费者、Broker三端的调优参数,并给出容量估算公式与官方压测工具的实战用法。
1. 性能模型:吞吐、延迟、持久化的三角
1.1 三大权衡
吞吐(Throughput)
/ \
/ \
延迟 持久化
(Latency) (Durability)
- 吞吐 vs 延迟:批处理、攒积攒积攒 → 吞吐高、延迟升;
- 持久化 vs 吞吐:acks=all + fsync 每消息 → 最稳、最慢;
- 不可兼得:先明确业务要什么,再定参数方向。
1.2 性能特征(SSD 时代经验值)
| 环节 | 特征 |
|---|---|
| 顺序写 | 单盘顺序写可达 几百 MB/s(比随机写高一个数量级) |
| 顺序读 | 页缓存命中率决定读吞吐 |
| 网络 | 千兆 ≈ 125MB/s,万兆 ≈ 1.25GB/s(常是上限) |
| 副本 | 3 副本写入放大 3×,带宽是隐含瓶颈 |
1.3 瓶颈定位口诀
CPU 高 → 压缩/序列化/协议开销(加密、压缩 CPU 密集)
磁盘 IO 高 → 写入跟不上(换 SSD / 降副本 / 缩消息)
网络高 → 跨机房/副本同步吃带宽
内存不足 → 页缓存小、GC 压力大
一句话:调优先定位瓶颈——SSD 时代顺序写很快,网络与副本放大往往是天花板;先想清楚要「高吞吐」还是「低延迟」,再动手拧参数。
2. 生产者调优:批量、压缩、重试、buffer
2.1 关键参数总览
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 32768); // 批量大小 32KB
props.put(ProducerConfig.LINGER_MS_CONFIG, 20); // 攒 20ms 再发
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "lz4"); // 压缩类型
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); // 发送缓冲 32MB
props.put(ProducerConfig.ACKS_CONFIG, "all");
props.put(ProducerConfig.RETRIES_CONFIG, 3);
props.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, 120000);
props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 5);
props.put(ProducerConfig.MAX_REQUEST_SIZE_CONFIG, 1048576);
2.2 参数解读
| 参数 | 作用 | 调优方向 |
|---|---|---|
batch.size | 单批最大字节 | 调大降 RPC 次数 |
linger.ms | 攒批等待时长 | 低延迟调小、高吞吐调大 |
compression.type | 压缩 | lz4/zstd 降带宽/磁盘,CPU 换 |
buffer.memory | 发送缓冲 | 背压窗口,太小易满 |
acks | 确认级别 | 1 或 all 看持久化需求 |
retries | 重试次数 | 配 delivery.timeout 配合 |
max.in.flight | 未确认请求数 | 调大提吞吐(幂等下 ≤5) |
max.request.size | 单请求上限 | 大消息需调大 |
2.3 低延迟 vs 高吞吐的典型配置
// 低延迟(订单支付、实时告警):linger 小、批小
props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 4096);
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "none");
// 高吞吐(埋点、日志管道):linger 大、压缩开
props.put(ProducerConfig.LINGER_MS_CONFIG, 100);
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 131072); // 128KB
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "zstd");
一句话:生产者的「吞吐旋钮」是 batch.size + linger.ms + compression——攒批越大、压缩越狠,吞吐越高、延迟越大;持久化另加 acks=all + 合理重试。
3. 消费者调优:fetch、并发、批量处理
3.1 关键参数总览
props.put(ConsumerConfig.FETCH_MIN_BYTES_CONFIG, 1); // 攒满才返回
props.put(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG, 500);
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500);
props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, 1048576);
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 300000);
3.2 参数解读
| 参数 | 作用 | 调优方向 |
|---|---|---|
fetch.min.bytes | 攒到多少字节才返回 | 调大减 RPC、提吞吐 |
fetch.max.wait.ms | 攒批最大等待 | 与上面配合 |
max.poll.records | 单次 poll 返回条数 | 调大提批量处理 |
max.partition.fetch.bytes | 单分区单次拉取上限 | 大消息需调大 |
max.poll.interval.ms | 处理超时上限 | 批量处理慢需调大 |
3.3 消费端吞吐的关键:并发度
消费者吞吐 = 分区数 × 单分区消费速率。三个并发层面:
① 分区数:Topic 分区数决定最大并行度
→ 消费者实例 ≤ 分区数
② 每实例多线程:单实例内线程池处理(注意偏移提交与顺序)
③ 批量处理:一次 poll 批量入库,避免逐条 RPC
3.4 批量入库示例
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(500));
// 批量插入,替代逐条处理
bulkInsert(records); // 攒一批 JDBC batch insert
consumer.commitSync(); // 成功后一次性提交
}
一句话:消费吞吐 = 分区并行度 × 批量处理——fetch 攒批 + 批量入库 + 分区对齐,是消费者提速三件套。
4. Broker 调优:磁盘、页缓存、分区数、segment
4.1 磁盘与页缓存
- 顺序写:Kafka 用顺序追加写日志,SSD 顺序写性能是关键;
- 页缓存:OS 页缓存撑读吞吐,给 Kafka 足够内存(别全给 JVM);
- JVM 堆:Kafka 堆主要用于业务对象,建议 4~6GB,把内存让给页缓存。
# broker 关键配置
num.network.threads=8
num.io.threads=8 # 处理请求的 IO 线程
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
log.flush.interval.messages=10000
log.segment.bytes=1073741824 # segment 1GB
log.retention.hours=168
log.dirs=/data/kafka1,/data/kafka2 # 多盘分散 IO
4.2 分区数设计
分区数 = 并行度上限,但不是越大越好:
分区数过低 → 消费并行度不足、单分区热点
分区数过高 → 元数据/文件句柄/协调开销上升
经验公式:
目标吞吐 / 单分区吞吐(≈10-20MB/s) ≈ 所需分区数
再考虑:峰值 / 3(副本放大)留余量
推荐起步:单 Topic 6~12 分区,压测后按瓶颈调整。分区数创建后只能增不能减,提前规划。
4.3 副本与 ISR
- 生产推荐 副本因子 3,
min.insync.replicas=2; - acks=all + min.insync=2 保证「两份 ISR 才成功」;
- 副本数越高,写入带宽放大越狠(3 副本 = 3× 写放大)。
4.4 Segment 与索引
log.segment.bytes大 → 索引小、文件少,但日志清理粗粒度;- 小消息高频写入:segment 过大浪费索引扫描;一般 1GB 为平衡点。
一句话:Broker 层 = 顺序写磁盘 + 页缓存吃肉(堆别大)+ 分区数规划 + 副本权衡——内存留给 OS、盘多点分散 IO、分区数按吞吐反推。
5. 压缩与序列化选型
5.1 压缩选型
| 压缩 | 压缩率 | CPU 开销 | 适用 |
|---|---|---|---|
none | 无 | 无 | 已压缩数据(图片、视频) |
gzip | 高 | 高 | 不敏感、带宽珍贵 |
snappy | 中 | 中 | 均衡 |
lz4 | 中 | 低 | 推荐默认 |
zstd | 最高 | 中 | 高吞吐省带宽 |
注意:文本 JSON 压缩收益大,已压缩的二进制收益小;压缩在批内生效,批越小压缩越不划算。
5.2 序列化选型
JSON:可读、慢、体积大
Avro/Protobuf:快、小、需 Schema Registry
String/ByteArray:最简,适合二进制载荷
高吞吐场景:Avro/Protobuf + 压缩 组合拳,体积与 CPU 双降。
5.3 压缩与批量配合
小消息 + 大 batch + zstd → 压缩率最高
大消息(>100KB)→ 压缩收益递减,评估是否值得
一句话:压缩与序列化是「性价比最高的调优」——lz4/zstd + Avro/Protobuf + 大 batch,相同硬件下吞吐能再上一个台阶。
6. 容量规划:估算公式与决策表
6.1 吞吐估算
场景:单 Topic 峰值 20万 msg/s,单条 1KB
单分区吞吐 ≈ 10-20 MB/s ≈ 1万-2万 msg/s
所需分区数 ≈ 20万 / 1.5万 ≈ 14 分区(留余量取 18-24)
写放大:3 副本 → 磁盘实际写入 = 20万 × 1KB × 3 ≈ 600MB/s
带宽:600MB/s > 千兆(125MB/s) → 需万兆或压缩(1KB→200B → 120MB/s ✅)
6.2 存储估算
retention 7 天,20万 msg/s × 1KB × 3 副本
= 200MB/s × 86400s × 7天 ≈ 120TB(原始未压缩)
压缩 5× → ≈ 24TB
规划时按「压缩后 × 1.5 余量」备盘
6.3 容量决策表
| 指标 | 估算要点 |
|---|---|
| 分区数 | 峰值吞吐 / 单分区吞吐 + 余量 |
| 磁盘容量 | 吞吐 × 保留时长 × 副本 × (1/压缩率) × 1.5 |
| 网络带宽 | 副本放大后的总吞吐,压不过就压缩 |
| Broker 数 | 总吞吐 / 单机吞吐,再留故障冗余 |
| 内存 | 页缓存应 > 热点读集大小 |
一句话:容量规划的公式是 吞吐 × 副本放大 × 保留时长 ÷ 压缩率——先算「磁盘与带宽」,再反推「分区数与 Broker 数」,余量别省。
7. 基准测试与压测方法
7.1 官方压测工具
# 生产者压测:发送 100 万条 1KB 消息
bin/kafka-producer-perf-test.sh \
--topic perf-test --num-records 1000000 \
--record-size 1024 --throughput -1 \
--producer-props bootstrap.servers=localhost:9092 \
acks=1 linger.ms=20 compression.type=lz4
# 消费者压测
bin/kafka-consumer-perf-test.sh \
--topic perf-test --messages 1000000 \
--threads 3 --broker-list localhost:9092
7.2 压测方法论
① 单测变量:一次只改一个参数,记录吞吐/延迟
② 梯度加压:记录 P50/P95/P99 延迟曲线,找拐点
③ 端到端验证:不只测单点,测「生产→消费」全链路
④ 持续观察:压测时看 CPU/磁盘/网络/GC 四象限
7.3 压测结果判读
| 现象 | 结论 |
|---|---|
| 吞吐上不去但 CPU 低 | 网络/磁盘瓶颈 |
| CPU 高、吞吐低 | 压缩/序列化/加密开销 |
| 延迟 P99 抖动 | 页缓存命中率、GC、分区热点 |
| 消息积压 | 消费者端批量/并行不足 |
一句话:压测是调优的方向盘——用官方工具梯度加压、一次一变、看四象限,用数据而不是猜决定下一步改哪个参数。
8. 常见坑与最佳实践
8.1 常见坑
| 坑 | 现象 | 对策 |
|---|---|---|
| 无脑调大 linger | 延迟翻倍 | 明确低延迟就别攒批 |
| 给 JVM 给满内存 | 页缓存饿死,读吞吐崩 | 堆 4-6GB,留给 OS |
| 分区数拍脑袋 | 后期无法减 | 按吞吐公式反推 |
| 压测只测生产端 | 消费端是瓶颈 | 端到端压测 |
| 已压缩数据再压缩 | 白耗 CPU | 用 none |
| 忽略 min.insync | acks=all 形同虚设 | 配 min.insync.replicas=2 |
8.2 最佳实践清单
- 先定位瓶颈再调参,一次一变;
- 内存留给页缓存,堆别贪大;
- 压缩开 lz4/zstd,性价比最高;
- 分区数按吞吐公式规划,留余量;
- 压测含端到端与延迟分布(P95/P99);
- 容量估算覆盖副本放大与压缩率。
9. 总结
本文搭建了 Kafka 性能调优的完整方法:
| 层 | 核心旋钮 |
|---|---|
| 生产者 | batch.size / linger.ms / compression / acks |
| 消费者 | fetch 攒批 / 批量入库 / 分区并行 |
| Broker | 页缓存 / 顺序写 / 分区数 / 副本 |
| 数据面 | lz4/zstd + Avro/Protobuf |
| 容量 | 吞吐×副本×保留÷压缩率 |
| 验证 | 官方压测 + 梯度加压 |
一句话记住:Kafka 调优是**「先定位瓶颈,再拧参数」**的科学——内存留给页缓存、压缩开 zstd、批量攒起来、分区按吞吐算,用官方压测工具验证每一步。吞吐和延迟不可兼得,先想清楚业务要哪个,Kafka 的默认配置才不是终点,而是起点。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。