2. 集群基础环境故障:操作系统层面排查与JVM调优
说实话,Kafka 出问题的时候,很多人第一反应就是看集群配置、看副本状态。但我个人的经验是——先看操作系统。你想想看,Kafka 说到底就是个 Java 进程,跑在操作系统上。操作系统要是出了问题,Kafka 再怎么折腾也没用。
这一章,我就带你过一遍操作系统层面的排查思路,以及 JVM 调优和 GC 问题的处理。这些都是我这些年踩过的坑,希望能帮你少走弯路。
2.1 CPU 排查:别被表象骗了
CPU 飙升是 Kafka 运维里最常见的现象。但 CPU 高不一定是坏事,关键要看是谁在吃 CPU。
2.1.1 快速定位 CPU 消耗者
我一般用这套命令组合拳:
# 查看整体 CPU 使用率
top -c
# 按 CPU 排序,找到最耗 CPU 的进程
top -bn1 -o %CPU | head -20
# 查看 Java 进程内的线程 CPU 消耗
top -H -p <kafka_pid>
# 把最耗 CPU 的线程 ID 转成十六进制
printf "%x\n" <thread_id>
# 用 jstack 查看线程堆栈
jstack <kafka_pid> | grep -A 30 <hex_thread_id>
top -c 看一眼,如果 Kafka 进程 CPU 超过 80%,立刻用 top -H -p 看线程级别。曾经有一次,我发现某个线程 CPU 跑到 99%,jstack 一看,是日志压缩线程卡住了。
2.1.2 常见的 CPU 问题场景
| 现象 | 可能原因 | 我的处理方式 |
|---|---|---|
| CPU 整体高,Kafka 进程正常 | 其他进程抢资源(如监控 agent、日志采集) | 用 nice 或 cgroups 限制非关键进程 |
| Kafka 进程 CPU 高,GC 线程活跃 | GC 频繁,内存不足或 GC 算法不合适 | 调整堆大小或切换 GC 算法 |
| Kafka 进程 CPU 高,业务线程阻塞 | 磁盘 I/O 瓶颈导致线程自旋等待 | 检查磁盘性能,考虑换 SSD |
| CPU 忽高忽低,波动剧烈 | 分区重平衡、消费者组频繁变动 | 检查消费者组状态,稳定分区分配 |
2.2 内存排查:堆内堆外都要看
Kafka 的内存使用分两块:JVM 堆内存和操作系统页缓存(Page Cache)。很多人只盯着堆内存,其实页缓存才是 Kafka 性能的关键。
2.2.1 查看内存使用
# 整体内存使用
free -h
# 查看页缓存和 buffer
cat /proc/meminfo | grep -E "^(MemTotal|MemFree|Cached|Buffers)"
# JVM 堆内存使用
jstat -gc <kafka_pid> 1000 5
# 查看堆内各代大小
jmap -heap <kafka_pid>
2.2.2 内存问题的典型表现
- 页缓存不足: 磁盘读请求变多,Kafka 从磁盘读数据而不是从内存读,延迟飙升。我遇到过一台机器页缓存只有 2GB,结果生产者的请求延迟从 5ms 涨到了 200ms。
- 堆内存溢出: 出现
OutOfMemoryError,通常是堆太小或内存泄漏。有一次我发现某个 topic 的消息体特别大,每条 10MB,堆很快就撑爆了。 - GC 频繁: 年轻代 GC 每秒几十次,老年代 GC 几分钟一次。这时候 CPU 也会跟着高。
我的经验: Kafka 的堆内存一般建议 4-6GB,不要超过 8GB。剩下的内存留给操作系统做页缓存。你想想看,Kafka 读写数据其实大量依赖页缓存,堆太大反而浪费。
2.3 磁盘排查:Kafka 的生命线
Kafka 是写磁盘的,磁盘性能直接决定了吞吐量。我见过太多因为磁盘问题导致的故障了。
2.3.1 磁盘性能检查
# 查看磁盘 I/O 统计
iostat -x 1 5
# 查看具体进程的磁盘读写
iotop -o -p <kafka_pid>
# 测试磁盘顺序写性能
dd if=/dev/zero of=/data/test bs=1M count=1024 oflag=direct
# 查看磁盘空间
df -h
2.3.2 磁盘问题的判断标准
| 指标 | 正常值 | 告警值 | 危险值 |
|---|---|---|---|
| 磁盘使用率 | < 70% | 70-85% | > 85% |
| 磁盘 I/O 等待时间(await) | < 10ms | 10-30ms | > 30ms |
| 磁盘队列长度(avgqu-sz) | < 2 | 2-5 | > 5 |
| 磁盘吞吐量(MB/s) | 取决于硬件 | 低于标称值 50% | 低于标称值 80% |
2.4 网络排查:看不见的瓶颈
网络问题往往是最难排查的。因为它的表现可能是延迟高、超时、连接断开,但原因可能千奇百怪。
2.4.1 网络排查命令
# 查看网络连接状态
ss -tpan | grep <kafka_port>
# 查看网络延迟
ping -c 10 <broker_ip>
# 查看带宽使用
iftop -i eth0 -n -P
# 查看丢包和错误
netstat -s | grep -E "segments|retransmit|drop"
2.4.2 常见的网络问题
- TCP 连接数过多: 每个生产者、消费者都会建立连接。如果连接数超过系统限制,新连接会被拒绝。我遇到过一台 broker 上有 5000 多个连接,结果系统报
Too many open files。 - 网络延迟高: 跨机房部署时,延迟可能到几十毫秒。这时候需要调整
request.timeout.ms和session.timeout.ms。 - 丢包: 会导致 TCP 重传,吞吐量骤降。用
netstat -s看重传率,超过 1% 就要警惕了。
2.5 JVM 调优与 GC 问题
Kafka 是 Java 写的,JVM 调优是绕不开的话题。我见过太多人直接用默认 JVM 参数跑 Kafka,结果 GC 成了瓶颈。
2.5.1 推荐的 JVM 参数
-Xms4g -Xmx4g # 堆大小,建议 4-6GB
-XX:+UseG1GC # 使用 G1 垃圾回收器
-XX:MaxGCPauseMillis=20 # 目标 GC 暂停时间
-XX:InitiatingHeapOccupancyPercent=35 # 触发并发 GC 的堆占用比例
-XX:+DisableExplicitGC # 禁用 System.gc()
-XX:+PrintGCDetails # 打印 GC 详情
-XX:+PrintGCDateStamps # 打印 GC 时间戳
-Xloggc:/var/log/kafka/gc.log # GC 日志路径
2.5.2 GC 问题的诊断
我一般用 jstat 和 GC 日志来诊断:
# 查看 GC 统计,每 1 秒输出一次
jstat -gcutil <kafka_pid> 1000
# 输出示例
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 45.23 60.12 85.34 78.21 1200 45.23 5 2.10 47.33
重点关注:
- YGC(年轻代 GC 次数):如果每秒超过 10 次,说明年轻代太小
- FGC(Full GC 次数):如果几分钟就一次,说明老年代快满了
- GCT(GC 总耗时):如果 GC 时间占总运行时间的 10% 以上,需要优化
我的建议: 如果 Full GC 频繁,先别急着调参数。先看看是不是堆内存泄漏——用 jmap -histo:live 看哪些对象占用了大量内存。我曾经发现某个 topic 的消费线程没关闭,导致一堆 KafkaConsumer 对象泄漏。
2.5.3 GC 算法选择
| GC 算法 | 适用场景 | 优缺点 |
|---|---|---|
| G1(推荐) | 堆大小 4-8GB,追求低延迟 | 暂停时间可控,但 CPU 开销稍高 |
| CMS | 堆大小 2-4GB,老版本 Kafka | 并发收集,但容易产生碎片 |
| Parallel | 堆大小 > 8GB,追求吞吐量 | 吞吐量高,但暂停时间长 |
| ZGC | 堆大小 > 16GB,JDK 11+ | 暂停时间极短,但还在实验阶段 |
2.6 知识体系总览
下面这张图,是我自己总结的操作系统层面排查思路。每次出问题,我就按这个流程走一遍,基本能定位到根因。
2.7 实战案例:一次 Full GC 引发的血案
最后分享一个我亲身经历的案例。有一次线上 Kafka 集群突然延迟飙升,生产者的请求超时率到了 30%。
排查过程:
- 先看操作系统: CPU 正常,内存正常,磁盘 I/O 正常,网络正常。
- 再看 JVM: 用
jstat -gcutil一看,Full GC 每 30 秒一次,每次暂停 5 秒。 - 分析 GC 日志: 发现老年代一直在增长,GC 后回收不了多少空间。
- 用 jmap 看堆:
jmap -histo:live发现byte[]占了 70% 的堆空间。 - 定位问题: 原来有个 topic 的消息体特别大,每条 5MB,而且消费者消费速度跟不上,消息全积压在 broker 的内存里。
解决方案:
- 临时把堆从 4GB 调到 8GB,缓解 GC 压力
- 增加消费者实例,加快消费速度
- 对超大消息做压缩处理
嗯,从那以后,我每次上线新 topic 都会先评估消息大小和消费速度,再也不敢大意了。