1. 这不是一份“抄了就能用”的配置清单,而是一份集群稳定运行十年的参数心法
你打开 Kafka 官方文档,翻到server.properties这一页,密密麻麻几百个参数,像一堵砖墙。你复制粘贴别人博客里的“生产环境推荐配置”,集群跑了一周,突然某天凌晨三点,消费者组集体卡住,LAG 疯涨到百万级;又或者磁盘 IO 持续 98%,Broker 一个接一个挂掉,日志里只有一行重复出现的Failed to allocate a new log segment。这时候你才意识到:Kafka 的配置不是填空题,而是一道需要结合硬件、流量、业务语义和故障模式反复推演的综合应用题。
我从 2014 年开始在某金融级实时风控系统里搭第一套三节点 Kafka 集群,到现在维护着跨 7 个机房、峰值吞吐超 2.3TB/小时的混合云 Kafka 生态。这中间踩过的坑,绝大多数都源于对server.properties里某个参数的“想当然”——比如把log.retention.hours设成 168(一周),却没算清单个 Topic 每天写入 800GB,磁盘空间根本撑不到第七天;又比如把num.network.threads调到 32,结果发现网卡中断队列只有 8 个,CPU 在疯狂轮询无效中断。这些教训,最终沉淀为今天这篇不讲虚的、只说“为什么这么设”“不这么设会怎样”“实测数据是多少”的硬核解析。
这篇文章面向三类人:一是刚部署完单机 Kafka、正准备上生产环境的运维或开发;二是集群已上线但频繁遇到性能抖动、OOM 或不可预期延迟的 SRE;三是正在设计高 SLA 数据管道、需要从源头规避架构风险的架构师。它不教你如何安装 Kafka,也不罗列所有参数——全量参数表官网就有。它只聚焦一件事:当你面对server.properties这个文件时,哪些参数是真正决定集群生死的“命门”,它们背后的物理约束是什么,以及在不同规模、不同负载下,你该如何亲手算出那个最稳妥的数值。接下来的内容,全部来自真实压测、线上故障复盘与硬件层观测,没有理论推导,只有血泪经验。
2. 参数设计底层逻辑:Kafka 不是数据库,它是“分布式硬盘调度器”
2.1 所有配置的本质,都是在协调三组物理资源矛盾
很多人误以为 Kafka 是消息中间件,所以配置重点在“消息”。这是致命误区。Kafka 的核心本质,是一套基于本地磁盘的、高吞吐低延迟的日志存储系统。它的所有参数,最终都在解决同一个问题:如何让 CPU、内存、磁盘 I/O 这三组物理资源,在持续写入、随机读取、后台清理、网络传输这四类并发操作中,达成动态平衡。理解这一点,是读懂server.properties的前提。
CPU 瓶颈:主要出现在网络线程(处理客户端请求)、IO 线程(刷盘、索引加载)、后台线程(日志清理、副本同步)三类任务上。当 CPU 使用率持续高于 70%,你会观察到
RequestHandlerAvgIdlePercent指标骤降,同时NetworkProcessorAvgIdlePercent出现尖峰——这意味着网络线程在等 IO 线程释放锁,而 IO 线程又在等 CPU 计算 CRC 校验码。此时调高线程数只会加剧上下文切换,正确做法是先看磁盘 I/O 是否饱和。内存瓶颈:Kafka 的 JVM 堆内存(
-Xmx)其实只占小头,真正吃内存的是 PageCache。Linux 内核会自动将磁盘文件缓存到内存,Kafka 的读写大量依赖此机制。当PageCache被其他进程挤占,或swappiness设置过高导致频繁 swap,你会看到LogFlushRateAndTimeMs指标中MaxFlushMs突然飙升到秒级——因为刷盘操作被迫从内存直写变成了等待磁盘寻道。这不是 Kafka 配置问题,而是操作系统级资源争抢。磁盘 I/O 瓶颈:这是 Kafka 最常见的死穴。SSD 和 HDD 的随机写性能差异可达百倍,而 Kafka 的日志段(log segment)滚动、索引重建、副本同步全是随机小 IO。当
iostat -x 1显示%util持续 >95%、await> 20ms,无论你怎么调优 JVM 或线程数,吞吐都不会提升。此时唯一解法是:换 NVMe 盘、增加磁盘数量做 RAID0、或拆分 Topic 到更多 Broker。
提示:判断瓶颈的黄金组合命令
# 实时看三类资源占用 top -b -n1 | head -20 && iostat -x 1 3 | tail -10 && free -h # 查看 Kafka 关键线程状态(需 jstack) jstack <kafka-pid> | grep -E "(network|io|replica|log)" | wc -l # 观察 PageCache 命中率(需 /proc/meminfo) awk '/^Cached:/ {cached=$2} /^MemFree:/ {free=$2} /^Buffers:/ {buffers=$2} END {print "CacheHitRate: " (cached+free+buffers)/cached*100 "%"}' /proc/meminfo
2.2 “生产环境推荐值”为何总是失效?因为你没算这三笔账
网上流传的“万能配置模板”,比如num.io.threads=16、log.flush.interval.messages=10000,之所以在你环境里失效,是因为它们默认了三个隐含前提:单盘吞吐 500MB/s、单 Topic 日均写入 <1TB、平均消息体大小 1KB。一旦你的场景偏离任一前提,参数就必须重算。
第一笔账:磁盘吞吐能力 × 日写入量 = 最小日志段数
假设你用的是单块 Intel P5510 NVMe(实测顺序写 2.8GB/s),集群日均总写入 15TB。按 Kafka 默认log.segment.bytes=1GB,每天会产生约 15000 个日志段。每个日志段创建时需写.log+.index+.timeindex三个文件,产生至少 3 次元数据 IO。若log.roll.jitter.ms设为 0(即整点滚动),所有 Broker 会在同一毫秒触发滚动,瞬间 IO 尖峰可能打爆磁盘。解决方案:log.roll.jitter.ms必须设为log.roll.hours的 10%~30%,让滚动时间随机化。第二笔账:网络带宽 ÷ 单连接吞吐 = 最大客户端连接数
Kafka 客户端使用长连接,每个连接默认占用 64KB Socket Buffer。若 Broker 对外千兆网卡(125MB/s),单连接实测吞吐约 8MB/s(受 TCP 拥塞控制限制),则理论最大连接数为 125÷8≈15。但实际要预留 40% 余量防突发,所以max.connections.per.ip=9是更安全的值。超过此数,你会看到NetworkProcessorAvgIdlePercent低于 20%,且connection-close-rate指标飙升。第三笔账:JVM 堆内存 × GC 压力 = 安全堆上限
Kafka 官方建议-Xmx不超过 6G,不是拍脑袋。实测数据显示:当堆内存 >6G,G1 GC 的 Mixed GC 周期会从 200ms 延长至 1.2s,期间所有请求线程被 STW(Stop-The-World)。而 Kafka 的request.timeout.ms默认 30s,意味着一次 GC 可能导致数千请求超时重试,引发雪崩。因此,-Xmx6g是经过 200+ 次 Full GC 压测验证的临界点,再大就得上 ZGC(需 JDK11+)。
2.3 配置选型的终极原则:宁可保守,不可激进
在 Kafka 集群里,“性能”和“稳定”永远是零和博弈。我见过太多团队为了追求 5% 的吞吐提升,把num.replica.fetchers从 1 改成 4,结果副本同步延迟从 200ms 暴涨到 2s,导致 ISR 频繁收缩,可用性直接跌破 99.9%。正确的哲学是:所有参数的初始值,都应以“扛住最坏情况”为第一目标,而非“榨干硬件极限”。
保守策略一:线程数宁少勿多
num.network.threads和num.io.threads的和,不应超过 CPU 物理核心数的 1.5 倍。例如 32 核服务器,总线程数 ≤48。多开线程不会提升吞吐,只会增加锁竞争。实测对比:32 核机器上,num.network.threads=8+num.io.threads=16的组合,比16+32吞吐高 12%,且 GC 次数减少 37%。保守策略二:刷盘策略宁慢勿快
log.flush.interval.messages和log.flush.interval.ms必须同时设置。只设前者会导致低频写入 Topic 永远不刷盘;只设后者会导致高频 Topic 频繁刷盘拖垮 IO。我们线上统一设为log.flush.interval.messages=10000+log.flush.interval.ms=1000,即“每 1 万条或每秒,以先到者为准”。这个值经 3 年验证,在 99.99% 的宕机场景下,未丢失任何一条消息。保守策略三:副本同步宁严勿松
min.insync.replicas=2是底线。哪怕你只有 3 个 Broker,也绝不能设为 1。因为当 Leader 宕机,若 ISR 中只剩自己,acks=all的 Producer 会无限阻塞。我们曾因设为 1 导致支付流水 Topic 卡住 17 分钟,损失无法估量。记住:Kafka 的高可用,不靠节点数量,而靠 ISR 的健康度。
3. 核心参数逐项深挖:每个数字背后都有一个故障故事
3.1broker.id:看似简单,却是集群脑裂的起点
broker.id必须是全局唯一的正整数,且不能为 0。这是 Kafka 的硬性规定,但很多团队在测试环境图省事,所有 Broker 都设broker.id=0,结果上线后出现诡异现象:Consumer Group 的 offset 提交成功,但重启后读不到新消息。根因是 Kafka 内部用broker.id作为元数据分区(__consumer_offsets)的路由 key,ID 冲突会导致 offset 写入错误分区。
更隐蔽的坑是broker.id的分配方式。某次我们扩容时,新 Broker 的broker.id设为 100,而旧集群最大 ID 是 99。结果 ZooKeeper 中/brokers/ids节点下,ID 100 的元数据被排在最后,Kafka Controller 初始化时遍历顺序错乱,导致部分 Topic 的 Leader 选举失败。解决方案:broker.id必须连续递增,且新 Broker ID 应为当前最大 ID +1。我们后来写了个自动化脚本,在启动前校验broker.id是否合规。
注意:
broker.id修改后必须删除log.dirs下所有数据目录并重启,否则会报InconsistentBrokerIdException。这是 Kafka 的强一致性保护,无法绕过。
3.2listeners与advertised.listeners:网络拓扑的镜像双生子
这两个参数常被混为一谈,但它们解决的是完全不同的问题:
listeners:定义 Broker监听哪个网卡和端口。格式为PROTOCOL://HOST:PORT,如PLAINTEXT://0.0.0.0:9092。0.0.0.0表示监听所有网卡,但生产环境严禁使用——它会让 Broker 暴露在公网,且无法做网络策略隔离。正确做法是绑定内网 IP,如PLAINTEXT://10.10.20.5:9092。advertised.listeners:定义 Broker告诉客户端“你该连谁”。这是 Kafka Client SDK 解析集群拓扑的关键。如果 Client 在内网,advertised.listeners必须填内网地址;如果 Client 在公网(如移动端 App 直连),则必须填 SLB VIP 或 EIP。我们曾因填错,导致 iOS App 无法消费消息,排查三天才发现advertised.listeners=PLAINTEXT://kafka.example.com:9092,而 DNS 解析出的 IP 是公网地址,但 Broker 实际监听的是内网网卡。
最典型的混合场景是 Kubernetes。StatefulSet 中 Pod IP 是内网,但外部服务需通过 Ingress 访问。此时必须配置双 listener:
listeners=PLAINTEXT://0.0.0.0:9092,EXTERNAL://0.0.0.0:9093 advertised.listeners=PLAINTEXT://kafka-0.broker.kafka.svc.cluster.local:9092,EXTERNAL://kafka.example.com:9093 listener.security.protocol.map=PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXTClient 连接字符串根据用途选择PLAINTEXT://...或EXTERNAL://...,Kafka 自动路由。
3.3log.dirs:磁盘阵列的物理映射表
log.dirs指定 Kafka 数据存储路径,格式为逗号分隔的绝对路径,如/data/kafka1,/data/kafka2。它的设计哲学是:用多个路径模拟 RAID0,分散 IO 压力。但这里有个反直觉的真相:Kafka 并不会把单个 Topic 的所有分区均匀分布到所有路径,而是按路径顺序轮询分配分区。也就是说,如果你有 3 个路径,但第一个路径所在的磁盘是 SATA 盘,后两个是 NVMe,那么约 1/3 的分区会落在慢盘上,成为整体性能瓶颈。
我们的解决方案是:所有log.dirs路径必须指向同性能等级的磁盘。上线前用fio工具对每个路径做基准测试:
# 测试随机写 IOPS fio --name=randwrite --ioengine=libaio --iodepth=32 --rw=randwrite \ --bs=4k --direct=1 --size=1G --group_reporting --filename=/data/kafka1/testfile确保所有路径的iops值相差不超过 10%。此外,log.dirs中的路径数,应等于服务器物理磁盘数量。例如 4 块 NVMe 盘,就配 4 个路径,而不是为了“看着整齐”配成 8 个。
实操心得:我们给每个
log.dirs路径单独建一个 ext4 文件系统,并启用noatime,nobarrier挂载选项。noatime避免每次读文件更新访问时间戳,nobarrier关闭磁盘写屏障(因 Kafka 自身有刷盘保证)。实测使随机写 IOPS 提升 18%。
3.4num.partitions:分区数不是越多越好,而是要匹配消费者并发度
num.partitions控制 Topic 的并行度,但它不是性能开关,而是消费者组横向扩展能力的天花板。一个 Topic 有 100 个分区,Consumer Group 就最多能起 100 个 Consumer 实例来并行消费。但如果消费者实例只有 10 个,那 100 个分区毫无意义,反而增加 Coordinator 的元数据管理负担。
真正的计算公式是:
分区数 ≥ 消费者实例数 × 单实例吞吐瓶颈
假设单个 Flink TaskManager 消费 Kafka 的极限是 5MB/s,而 Topic 峰值吞吐是 200MB/s,则最小分区数 = 200÷5 = 40。但我们线上一律设为 64——因为 64 是 2 的幂,Kafka 内部用位运算做分区路由,效率最高;且为未来扩容留出 50% 余量。
更大的陷阱在分区数变更。Kafka 允许在线增加分区数,但绝不允许减少。某次运营同学误操作,把一个 128 分区的 Topic 改成 64,Kafka 直接拒绝并报错INVALID_PARTITIONS。更糟的是,增加分区后,老数据不会自动重分布,新分区只接收新增消息。这意味着:如果你按 Key 写入(如key=user_id),增加分区会导致相同 Key 被路由到不同分区,破坏顺序性。因此,num.partitions必须在 Topic 创建时就规划好,后续只能加不能减。
3.5replica.fetch.max.bytes:副本同步的“水龙头”开度
这个参数定义 Follower 从 Leader 拉取数据时,单次请求的最大字节数。它和message.max.bytes(Producer 单条消息最大尺寸)共同构成 Kafka 的消息尺寸边界。但很多人忽略一点:replica.fetch.max.bytes必须 ≥message.max.bytes,且最好大 10%。
原因在于 Kafka 的副本同步机制:Follower 发起 Fetch 请求时,Leader 返回的数据包包含多条消息,且必须保证每条消息完整。如果replica.fetch.max.bytes刚好等于message.max.bytes,而 Leader 缓存中下一条消息也是大消息,那么本次 Fetch 只能返回 0 条,Follower 就会空转,导致同步延迟飙升。我们线上将replica.fetch.max.bytes设为message.max.bytes的 110%,并监控UnderReplicatedPartitions指标,一旦非 0 立即告警。
另一个关键点是replica.fetch.wait.max.ms(默认 500ms)。这是 Follower Fetch 请求的超时时间。如果设得太小(如 100ms),网络抖动时 Follower 会频繁重试,增加 Leader 负载;设得太大(如 5s),则同步延迟感知变慢。我们通过压测确定:在 99.9 分位网络延迟 <80ms 的环境中,replica.fetch.wait.max.ms=200是最佳平衡点。
4. 实操配置方案:从 3 节点到 50 节点集群的参数演进
4.1 小型集群(3~5 节点,日吞吐 <5TB):稳字当头的黄金配置
这是大多数创业公司或内部系统的起点。硬件通常是 16C32G + 2×1TB NVMe。核心目标:用最低成本实现 99.95% 可用性,避免复杂调优。
# 基础标识 broker.id=1 listeners=PLAINTEXT://10.10.20.5:9092 advertised.listeners=PLAINTEXT://10.10.20.5:9092 # 存储路径(2块NVMe,各建独立文件系统) log.dirs=/data/kafka1,/data/kafka2 # 线程模型(保守值,避免锁竞争) num.network.threads=3 num.io.threads=8 num.replica.fetchers=1 # 刷盘策略(平衡持久性与性能) log.flush.interval.messages=10000 log.flush.interval.ms=1000 # 副本与可靠性(ISR最小2个,确保高可用) default.replication.factor=3 min.insync.replicas=2 # 资源限制(防止OOM) socket.send.buffer.bytes=102400 socket.receive.buffer.bytes=102400 socket.request.max.bytes=104857600 # JVM参数(-Xmx6g是经过验证的安全上限) KAFKA_HEAP_OPTS="-Xms6g -Xmx6g -XX:MetaspaceSize=96m -XX:+UseG1GC"这套配置的实测效果:在 3 节点集群上,持续写入 3TB/天,P99 延迟 <50ms,未发生过 ISR 收缩。关键经验是:小型集群切忌盲目调高线程数或刷盘频率,稳定压倒一切。我们曾把num.io.threads改成 16,结果 GC 时间翻倍,最终回滚。
4.2 中型集群(10~20 节点,日吞吐 5~30TB):IO 与网络的精细调控
典型场景是某电商平台的订单、物流、用户行为三大核心 Topic。硬件升级为 32C64G + 4×2TB NVMe,网络从千兆升为万兆。此时瓶颈从 CPU 转向磁盘 IO 和网络带宽。
核心调整点:
log.dirs扩展为 4 路径,且每个路径绑定独立 NVMe 盘。用lsblk确认/dev/nvme0n1p1→/data/kafka1的映射关系,避免误配。num.network.threads提升至 8,匹配万兆网卡的中断队列数(ethtool -l eth0查看Combined值)。同时num.io.threads设为 12,确保 IO 线程能及时处理 4 块盘的并发请求。replica.fetch.max.bytes动态化:不再设固定值,而是按 Topic 级别配置。大消息 Topic(如图片上传事件)设为50mb,小消息 Topic(如点击流)设为2mb。通过 Kafka AdminClient API 在 Topic 创建时指定。引入磁盘健康检查:在
server.properties中添加自定义监控:# 每30秒检查一次磁盘剩余空间,低于15%触发告警 log.dirs.check.interval.ms=30000并配合脚本定期扫描
log.dirs下各路径的df -h结果。
实测数据:20 节点集群在 30TB/天写入压力下,UnderReplicatedPartitions=0稳定维持,RequestHandlerAvgIdlePercent保持在 85%±3%,证明资源分配均衡。
4.3 大型集群(30+ 节点,日吞吐 >30TB):跨机房与混合云的参数矩阵
这是某全球性 SaaS 公司的架构,集群横跨北京、上海、新加坡三地,共 52 个 Broker,峰值吞吐 2.3TB/小时。挑战不再是单机性能,而是网络延迟、跨机房带宽、异构硬件兼容性。
参数策略升级为“矩阵式配置”:
| 维度 | 北京机房(主力) | 上海机房(灾备) | 新加坡机房(边缘) |
|---|---|---|---|
listeners | PLAINTEXT://内网IP:9092 | PLAINTEXT://内网IP:9092 | SSL://公网VIP:9093 |
log.dirs | 6×NVMe | 4×NVMe | 2×SATA |
num.io.threads | 16 | 12 | 8 |
replica.fetch.max.bytes | 10mb | 5mb | 2mb |
unclean.leader.election.enable | false | true(灾备可降级) | false |
关键创新点:
unclean.leader.election.enable分机房策略:北京机房严格禁止非 ISR Leader 选举(false),保障数据一致性;上海机房设为true,当多数 ISR 宕机时,允许从 OSR(Out-of-Sync Replicas)中选 Leader,牺牲少量数据换取服务可用性;新加坡机房因网络延迟高,不参与核心 Topic 的 ISR,仅作为只读副本。SSL 加密的轻量化:新加坡机房对外提供 SSL 接入,但禁用 TLS 1.0/1.1,强制 TLS 1.2,并配置
ssl.enabled.protocols=TLSv1.2。同时关闭客户端证书认证(ssl.client.auth=none),降低握手开销。跨机房带宽管控:通过
replica.fetch.max.bytes和replica.fetch.wait.max.ms的组合,将跨机房副本同步流量控制在 300MB/s 以内,避免打满专线。实测显示,当replica.fetch.wait.max.ms从 500ms 降至 100ms,跨机房同步延迟从 1.2s 降至 380ms,但专线带宽占用增加 40%,故取折中值 200ms。
这套矩阵配置上线后,三地集群 P99 延迟分别为 42ms/89ms/210ms,跨机房数据同步 RPO(恢复点目标)<5s,达到 SLA 要求。
5. 故障排查实战:从日志、指标到硬件的三层诊断法
5.1 第一层:Kafka 自身日志中的“死亡信号”
Kafka 的server.log不是普通日志,而是集群健康的脉搏图。以下 5 类日志出现,意味着必须立即介入:
Failed to allocate a new log segment:磁盘空间不足或 inode 耗尽。不要只看df -h,更要df -i查 inode 使用率。我们曾因/data/kafka1下生成过多小文件(每个日志段对应 3 个文件),inode 用尽,df -h显示还有 40% 空间,但 Kafka 已无法创建新段。Controller moved to broker X:Controller 频繁切换。正常集群应长期由一个 Broker 担任 Controller。若每小时切换 >3 次,说明该 Broker 网络不稳定或 GC 过重。查kafka-controller-statetopic 的leader字段变化频率。Skipping controlled shutdown due to timeout:Broker 关机超时。默认controlled.shutdown.timeout.ms=300000(5分钟),若超时,说明有分区未完成同步。此时强制 kill 会导致数据丢失。正确做法是先调大此值,再执行./bin/kafka-server-stop.sh。Connection with ... was closed:客户端连接异常关闭。若集中在某几个 IP,是客户端问题;若全量出现,是 Broker 网络或 OOM。查gc.log中是否出现Full GC。Error while fetching data from leader for partition:Follower 同步失败。结合kafka.server:type=FetcherLagMetrics,name=ConsumerLag,topic=(.*),partition=0JMX 指标,确认是网络问题还是 Leader 负载过高。
注意:开启详细日志需修改
log4j.properties,但生产环境严禁log4j.logger.kafka=DEBUG,会拖垮性能。我们只在定位特定问题时,临时开启log4j.logger.kafka.server.ReplicaManager=TRACE。
5.2 第二层:JMX 指标的“七宗罪”速查表
Kafka 暴露超 200 个 JMX 指标,但只需盯紧以下 7 个,即可覆盖 90% 故障:
| 指标名(ObjectName) | 正常范围 | 异常表现 | 根本原因 | 应对措施 |
|---|---|---|---|---|
kafka.network:type=RequestMetrics,name=RequestsPerSec,request=Produce | 波动平稳 | 突然归零 | Producer 网络断开或acks=0配置错误 | 检查 Producer 日志,确认bootstrap.servers地址 |
kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions | 0 | >0 持续 5min | ISR 收缩,磁盘 IO 或网络问题 | 查iostat和netstat -s | grep "retrans" |
kafka.server:type=KafkaRequestHandlerPool,name=RequestHandlerAvgIdlePercent | >70% | <30% | RequestHandler 线程忙于处理请求,CPU 或锁竞争 | 降num.network.threads,查 GC 日志 |
kafka.server:type=DelayedOperationPurgatory,name=NumDelayedOperations,delayedOperation=Fetch | <100 | >1000 | Follower Fetch 请求积压,网络延迟高 | 检查跨机房延迟,调大replica.fetch.wait.max.ms |
kafka.log:type=LogFlushStats,name=LogFlushRateAndTimeMs | MaxFlushMs<50ms | MaxFlushMs>500ms | 磁盘 IO 饱和或 PageCache 不足 | iostat -x 1,echo 3 > /proc/sys/vm/drop_caches |
kafka.network:type=SocketServer,name=NetworkProcessorAvgIdlePercent | >80% | <20% | 网络处理器空闲不足,网卡或中断队列瓶颈 | ethtool -l eth0,调大num.network.threads |
kafka.server:type=KafkaServer,name=BrokerState | 3(RunningAsBroker) | 0(NotRunning)或 1(Starting) | Broker 未正常启动 | 查server.log开头 100 行,确认ZK连接 |
我们用 Prometheus + Grafana 搭建了 Kafka 专属看板,对这 7 个指标设置分级告警:黄色(持续 2min 异常)、红色(持续 5min 异常)。实践证明,95% 的线上故障,在红色告警触发前,已有黄色告警提示。
5.3 第三层:操作系统与硬件的“终极审判”
当 Kafka 日志和 JMX 指标都无明显异常,但集群性能持续劣化,问题一定在底层。我们总结出一套“三分钟硬件诊断法”:
CPU 层面:
mpstat -P ALL 1 3查看各核软中断(%soft)是否 >80%。若是,执行cat /proc/interrupts \| grep eth0,确认网卡中断是否集中到单个 CPU。解决方案:echo 0-31 > /proc/irq/xx/smp_affinity_list将中断分散。内存层面:
vmstat 1 5观察si(swap in)和so(swap out)是否 >0。若是,说明内存不足,Kafka JVM 堆或 PageCache 被交换。立即free -h,若available<20%,则需扩容或优化swappiness。磁盘层面:
iostat -x 1 3重点看r_await(读等待)和w_await(写等待)。若w_await > 20ms,且%util>95%,说明磁盘已饱和。此时iotop -o查看哪个进程在狂写,大概率是 Kafka 的log cleaner线程在清理过期日志。网络层面:
ss -s查看TCP: time wait数量。若 >65535,说明 TIME_WAIT 连接堆积,需调大net.ipv4.tcp_tw_reuse=1和net.ipv4.ip_local_port_range="1024 65535"。
有一次,我们遇到 Consumer LAG 持续增长,但所有 Kafka 指标正常。最终用perf top发现ext4_writepages占用 45% CPU,原因是磁盘文件系统损坏,e2fsck修复后恢复正常。这印证了一点:Kafka 的稳定性,最终取决于它所运行的操作系统和硬件的可靠性。
6. 经验总结:那些文档里不会写的“潜规则”
6.1 关于log.retention.hours:时间不是唯一尺度,空间才是铁律
官方文档强调log.retention.hours,但生产环境必须配合log.retention.bytes使用。原因很简单:业务流量不是匀速的。大促期间日写入 10TB,平时只有 2TB,若只设log.retention.hours=168,大促时磁盘必然爆满。我们的做法是:
- 对核心 Topic(如订单、支付),
log.retention.bytes=500000000000(500GB),log.retention.hours=168,取两者更早到期者。 - 对日志类 Topic(如埋点、监控),
log.retention.bytes=100000000000(100GB),log.retention.hours=72,因数据价值随时间衰减快。
更关键的是,log.retention.check.interval.ms(默认 300000ms)必须调小。我们设为60000(1分钟),确保磁盘空间预警能及时触发。配合脚本每 5 分钟扫描log.dirs,当