做Kafka集群这件事,我发现很多人的顺序是反的:上来就装包、改参数,最后压测不达标再回头怀疑机器不行。实际上,在Oracle Linux 8.4上搭建Kafka集群并不难,难的是在“高吞吐量、实时数据流处理”这个目标下,把操作系统、Broker、生产者、消费者四个层面的参数一次调对。我最近刚交付完一套基于Oracle Linux 8.4的Kafka集群,从环境规划到压测调优踩了不少坑,这篇文章就把完整过程拆开来讲,包含部署命令、核心配置、参数取舍、压测方法和故障排查,你可以直接照着操作,也能避开那些文档里不会写的坑。
1. 开始之前:先想清楚集群规划这盘棋
1.1 版本选型与节点规划
Oracle Linux 8.4的系统版本不算新,但胜在稳定,生命周期长,内核默认是UEK R7(5.4.x),和普通RHEL 8系列完全兼容。选择它做Kafka底座,主要看重的是它在生产环境里的内核稳定性,以及不用折腾驱动兼容性。Kafka本身是Java应用,不依赖内核特性,所以UEK还是兼容内核都无所谓,系统层面重点是内核参数调优,这部分后面专门讲。
Kafka版本我选了kafka_2.13-3.4.1,搭配OpenJDK 11。为什么不用最新的3.6或4.0?因为3.4这个版本在ZooKeeper模式下非常成熟,社区踩坑记录多,网上能搜到的运维经验也最丰富。Kafka 3.4还支持KRaft模式(去ZooKeeper),但我生产上没用,原因很简单:团队运维习惯还停留在ZooKeeper模式,而且KRaft当时还在持续迭代,我不想在依赖核心的组件上赌太新的东西。如果你从头搭建自己的环境,可以测试KRaft,但参考这篇文章时建议先按ZooKeeper模式走,稳妥第一。
节点规划上,我强烈建议最少3个Broker节点,这也是Kafka生态最经典的部署规模:
| 节点 | 角色 | 配置建议 |
|---|---|---|
| node1 | Broker + Zookeeper | 16C / 32G内存 / 2块NVMe SSD |
| node2 | Broker + Zookeeper | 16C / 32G内存 / 2块NVMe SSD |
| node3 | Broker + Zookeeper | 16C / 32G内存 / 2块NVMe SSD |
3个节点意味着副本因子可以设置为3,任何一个节点宕机,数据依然有2个副本在线,配合min.insync.replicas=2,生产写入不会中断。CPU方面,Kafka是CPU密集和IO密集混合型应用,压缩、解压、网络读写都会吃CPU,16核起步没什么好犹豫的。存储则必须用独立数据盘,不要和系统盘混在一起,否则磁盘IO竞争会直接在压测时暴露出来。
磁盘容量可以按这个公式估算:日新增数据量 × 保留天数 ÷ 副本数,再乘以1.4的缓冲系数。举例来说,如果每天Topic产生800GB数据,保留3天,副本因子为3,单台节点存储估算为:800GB × 3天 ÷ 3副本 × 1.4 ≈ 1.1TB。这里还要额外预留30%给后台清理和压缩操作,别把容量卡得太死。
1.2 操作系统层面先做对几件事
装好系统后,别急着装Kafka,先处理操作系统层的一堆默认值。这些默认值是为通用服务器设计的,对高吞吐Kafka来说全是阻碍。
第一件事是关闭透明大页(Transparent Huge Pages)。Kafka的日志段文件大量使用内存映射,透明大页会导致内存分配延迟抖动,直接表现就是请求延迟毛刺。关闭方法如下:
# 临时关闭 echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag # 永久生效:写入 /etc/rc.d/rc.local cat >> /etc/rc.d/rc.local << EOF echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag EOF chmod +x /etc/rc.d/rc.local第二件事是调整swap策略。Kafka高吞吐的关键之一就是Page Cache,也就是把热数据留在内存里,让磁盘顺序读写变成内存读写。如果系统频繁触发swap,Page Cache被换出,性能会断崖式下跌。我把vm.swappiness调到1,并不是彻底禁用swap,而是告诉内核“能不用swap就尽量别用”,给Page Cache留足空间。
echo 'vm.swappiness = 1' >> /etc/sysctl.conf sysctl -p第三件事是修改文件句柄和线程数限制。Kafka Broker需要维持大量网络连接,单机上几千个文件描述符是常态,除非你跑了不到3个Broker。修改/etc/security/limits.conf:
cat >> /etc/security/limits.conf << EOF kafka soft nofile 1048576 kafka hard nofile 1048576 kafka soft nproc 65535 kafka hard nproc 65535 EOF如果你用的systemd管理服务(我后面会讲),还要在service文件里显式设置LimitNOFILE=1048576,否则limits.conf对systemd服务不生效,这是个很容易踩的坑。
第四件事是磁盘挂载参数。数据盘挂载时加上noatime,nodiratime,减少访问时间戳的写入。SSD的IO调度器建议设为none,让NVMe走自己的硬件队列,避免内核调度器干扰:
# 对NVMe SSD设备 echo none > /sys/block/nvme0n1/queue/scheduler1.3 网络与安全配置要点
Kafka节点间通信主要用两个端口:Broker之间的9092(默认),ZooKeeper的2181。如果是多节点集群,Broker之间的内网流量非常夸张,尤其是副本复制和消费者拉取阶段,千兆网卡基本不够用,建议至少万兆。这听起来奢侈,但高吞吐目标下,网络是绕不过去的瓶颈。
防火墙和SELinux,我建议这样处理:如果是内网专用环境,可以直接放行指定IP段;如果必须开SELinux,那就要为Kafka监听端口单独配置策略,但说实话很费劲,生产环境里大多数团队在同等条件下直接关闭SELinux。我倾向于更务实的方案,用firewall-cmd做白名单放行:
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/24" port port="9092" protocol="tcp" accept' firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/24" port port="2181" protocol="tcp" accept' firewall-cmd --reload特别要注意的是advertised.listeners配置。Kafka客户端连接的是advertised.listeners里暴露的地址,不一定是Broker本机的网卡IP。很多新手在这里把地址配成了localhost,客户端当然连不上。多网卡环境下,我会明确指定数据网卡IP,不要让Kafka自己去猜。
2. 部署Kafka Broker:从解压到开机自启
2.1 安装JDK并部署二进制包
Oracle Linux 8.4的Yum源里有OpenJDK,直接装:
yum install -y java-11-openjdk-devel java -version注意装的是-devel版本,不只是JRE。Kafka启动时需要完整的JDK工具链,某些监控脚本也要用到jconsole、jstack这些工具。
Kafka安装包从官网下载二进制压缩包,这里有一个小建议:不要用发行版自带的Kafka包,版本通常滞后,而且打包路径很乱,出了问题没法对照官方文档。我习惯把Kafka装在/opt/kafka,数据目录放在/data/kafka,日志目录放在/data/kafka/logs,路径规划清晰,后续维护少很多麻烦。
# 创建专用用户 useradd -r -s /sbin/nologin kafka mkdir -p /data/kafka/logs chown -R kafka:kafka /data/kafka # 解压到 /opt/kafka tar -zxvf kafka_2.13-3.4.1.tgz -C /opt/ mv /opt/kafka_2.13-3.4.1 /opt/kafka mkdir -p /opt/kafka/logs chown -R kafka:kafka /opt/kafka2.2 server.properties逐项拆解
每个Broker节点的/opt/kafka/config/server.properties是核心配置文件,我的参考配置如下:
broker.id=1 listeners=PLAINTEXT://10.0.0.11:9092 advertised.listeners=PLAINTEXT://10.0.0.11:9092 log.dirs=/data/kafka/data num.network.threads=6 num.io.threads=16 socket.send.buffer.bytes=1024000 socket.receive.buffer.bytes=1024000 socket.request.max.bytes=104857600 num.partitions=12 default.replication.factor=3 offsets.topic.replication.factor=3 transaction.state.log.replication.factor=3 transaction.state.log.min.isr=2 log.retention.hours=72 log.segment.bytes=1073741824 log.retention.check.interval.ms=300000 auto.create.topics.enable=false unclean.leader.election.enable=false逐个说下关键项和我的调参逻辑:
broker.id必须全局唯一,这个不用解释。listeners和advertised.listeners我直接写内网IP,避免Kafka在启动时自动解析主机名出错。如果你用主机名,必须在/etc/hosts里写好映射,否则跨节点通信时解析不到IP,各种超时满天飞。
log.dirs是日志数据目录,可以配置多个路径用逗号分隔。Kafka会把不同分区的数据分散到多个目录,相当于软件层面的磁盘条带化。如果节点上有多块数据盘,我建议都用上,吞吐会有可感知的提升。但是要注意,不能把Page Cache所在的系统盘混进来。
num.network.threads和num.io.threads这两个参数后面优化章节会细说。这里先按6和16配置,实际压测后再微调。socket.send.buffer.bytes和socket.receive.buffer.bytes我调大到了1MB左右,这个参数影响网络吞吐,默认值偏保守。
num.partitions=12是新建Topic的默认分区数。分区数不是越多越好,每个分区会占用一定的文件句柄和内存,而且分区太多会导致分区leader分布不均。12个分区在3节点集群上,每个节点平均4个leader,分布比较均衡。官方建议分区数不超过Broker数 × 磁盘吞吐系数,什么场景配多少分区,后面压测部分再讲。
auto.create.topic.enable=false是我强烈建议关掉的参数。谁也不想某一个客户端手滑往不存在的Topic发消息,结果集群自动创建了一堆垃圾分区,既占内存又难清理。Topic规范化创建,写进发布流程,比什么参数调优都重要。
生产环境可靠性相关参数,unclean.leader.election.enable=false必须保留默认,确保leader不会从一个落后过多的副本中选举产生,宁可短暂不可用,也不能丢数据。
2.3 用systemd管理Broker进程
直接nohup kafka-server-start.sh这种方式启动,进程死了没人拉起来,提交一次发布代码忘了带-daemon参数还会导致SSH挂住。我建议使用systemd统一管理,实现开机自启、崩溃自动拉起、日志journal化统一采集。
创建/etc/systemd/system/kafka.service:
[Unit] Description=Apache Kafka Server After=network.target zookeeper.service [Service] Type=forking User=kafka Group=kafka Environment=JAVA_HOME=/usr/lib/jvm/java-11-openjdk Environment=KAFKA_HEAP_OPTS="-Xms6g -Xmx6g" ExecStart=/opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/server.properties ExecStop=/opt/kafka/bin/kafka-server-stop.sh LimitNOFILE=1048576 Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target这里有几个细节值得注意。Type=forking是因为kafka-server-start.sh不带-daemon时本身就会fork后台进程,systemd识别主进程退出、子进程存活的状态。环境变量KAFKA_HEAP_OPTS用来控制JVM堆大小,后面优化章节会展开。LimitNOFILE=1048576在service文件里显式声明,这就是前面提到的systemd服务不读limits.conf的坑。
启动并验证:
systemctl daemon-reload systemctl start kafka systemctl enable kafka systemctl status kafka如果还要用ZooKeeper做协调服务,同样写一个zookeeper.service。我给ZooKeeper单独限定了内存,避免它和Kafka争抢堆内存:
[Unit] Description=Apache ZooKeeper Server After=network.target [Service] Type=forking User=kafka Group=kafka Environment=ZOO_LOG_DIR=/data/kafka/logs ExecStart=/opt/kafka/bin/zookeeper-server-start.sh /opt/kafka/config/zookeeper.properties ExecStop=/opt/kafka/bin/zookeeper-server-stop.sh LimitNOFILE=1048576 Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target3. 高吞吐优化:不是堆参数,是理解参数
3.1 Kafka高吞吐的底层来源
先说清一件事:Kafka的吞吐能力,很大程度上不是靠JVM堆,而是靠操作系统Page Cache和磁盘顺序写。Kafka消息先写入Page Cache,后台再批量刷到磁盘,消费者读取时优先命中Page Cache,命中不了再从磁盘顺序读。这决定了高吞吐场景下的核心策略——让Page Cache尽量大、让磁盘写入尽量顺序化、让网络缓冲尽量宽裕。
这也是为什么我强调JVM堆不要给太大。如果给Kafka的JVM堆分配了16GB,那么留给Page Cache的内存就只剩16GB(假设物理内存32GB)。而Kafka的堆主要用来存放元数据、索引、请求缓冲等,6GB就足够了。堆设得越大,Full GC时间越长,反而会把原本可以躺在Page Cache里的热数据挤出去,吞吐不升反降。这一点是很多人踩过的坑。
零拷贝(sendfile)也是Kafka高吞吐的来源之一。消费者拉取消息时,数据可以从Page Cache直接通过网卡发送,不经过用户态和JVM堆复制。理解这一点后你就会明白,消费者消费速度的瓶颈,通常不在Kafka本身,而在消费端的处理逻辑和网络带宽。
3.2 Broker端参数优化
我调整Broker参数时遵循一个顺序:先调CPU和网络线程,再调Socket缓冲,最后检查副本相关的参数。
num.network.threads负责处理网络请求的线程数,默认是3。这个参数跟Broker所在机器的核数直接相关,我在16核机器上先用6,因为网络线程是IO密集型,给多了只会增加上下文切换。num.io.threads默认是8,我设为16,因为Kafka的磁盘IO操作由这些线程执行,在SSD上稍微加大有助于并行处理多个分区请求。这两个参数不是越大越好,压测时如果发现CPU打到90%以上,说明线程数已经够用,再加没有意义。
queued.max.requests在网络线程和IO线程之间做缓冲,默认500。如果磁盘暂时跟不上请求,这个队列能兜住一定量的请求,避免客户端直接超时。我只在高写入场景下调到1000,数值太大反而会增加延迟。
num.replica.fetchers是副本同步线程数,默认1,我直接调到4。这是很多调优文章容易忽略的参数。当集群有多个Topic、多个分区时,Follower副本从Leader拉取数据的速度很关键。1个线程意味着所有副本的同步请求串行,遇到大流量时ISR会频繁收缩。调大后,单台节点上的副本同步可以并行进行,配合万兆内网,同步速度基本跟得上写入速度。
log.flush.interval.messages和log.flush.interval.ms控制日志刷盘频率,Kafka默认依赖操作系统刷盘,这是对的。手动把刷盘间隔调得过短会直接废掉Page Cache的优势,把随机写变成常态。保持默认或者调大刷盘间隔,可靠性交给副本机制来保障,这是Kafka的设计哲学。
3.3 生产者参数优化
生产者是Kafka吞吐的第一入口,这里参数调整对吞吐量的影响最立竿见影。
batch.size控制生产者批量发送的缓冲区大小,默认16KB,对于高吞吐场景太小。我压测时逐步调到64KB甚至128KB,批量越大,单次网络请求携带的数据越多,单位时间能发送的消息数越高。但要注意,如果Topic的消息都很小,批次可能凑不满就要发送,这时候光调batch没效果,还得配合linger.ms。
linger.ms控制消息在缓冲区等待的时间,默认0,也就是有消息就立刻发,这样batch基本形同虚设。我调成10到20毫秒,让生产者多花十几毫秒积攒一个批次。这是吞吐和延迟的经典权衡:高吞吐场景下20ms的额外延迟完全可接受,换来的是吞吐量可能翻倍。你可以观察压测结果逐步调整,我的经验是batch.size=65536, linger.ms=10是一个不错的起点。
compression.type对吞吐的提升异常明显。消息压缩后网络传输量大幅下降,CPU换带宽非常划算。我生产环境用的lz4,压缩速度快、CPU开销低,压测中相比不压缩大约有30%以上的吞吐提升。如果消息内容重复度高,zstd压缩率更好,但CPU开销略高,建议压测对比后再决定。
acks和retries是最需要权衡的一组参数。acks=1时生产者只要leader写入成功就返回,吞吐最高,但leader宕机时可能丢消息。acks=all时所有ISR副本都写入才返回,可靠性最高,吞吐会低一些。我压测后发现,在内网环境、副本同步正常时,acks=all配合min.insync.replicas=2的吞吐下降并不显著,为了数据安全,我选了acks=all。
这里给一个参考配置:
acks=all retries=3 batch.size=65536 linger.ms=10 compression.type=lz4 max.in.flight.requests.per.connection=5 enable.idempotence=truemax.in.flight.requests.per.connection=5允许客户端在未收到确认前发送最多5个请求,对吞吐有利。如果你同时开了幂等性,乱序问题由Kafka自身解决,不用担心多次重试导致消息顺序错乱。
3.4 消费者参数优化
消费者端吞吐优化容易被忽视,但消费速度上不去,最后就是Lag堆积,整个实时链路变成假实时。
fetch.min.bytes默认1字节,意味着Broker有一点点数据就返回,这会导致消费者频繁发起拉取请求,网络往返开销巨大。我设为1KB甚至4KB,让Broker攒够一批数据再返回。配合fetch.max.wait.ms=500,即使数据量不够,最多等500毫秒也会返回,兼顾了延迟。
max.partition.fetch.bytes控制单个分区单次拉取的最大字节数,默认1MB,对高吞吐场景勉强够用。如果业务消息体量大,建议调大到2MB。这里要注意,总值等于max.partition.fetch.bytes乘以分区数,如果分区多,单次拉取会使用大量内存。
enable.auto.commit=false是我一直坚持的配置。自动提交虽然省事,但可能在消息处理崩溃后造成数据丢失,或者处理超长任务时被阻塞导致rebalance。手动提交虽然复杂一点,但能把提交时机和消费完成绑定,防止重复消费和数据丢失。都做实时数据流了,这点可靠性投入不能省。
消费者组的消费者数量不要超过分区的数量。比如Topic有12个分区,你起了16个消费者,那4个消费者会一直空转,浪费资源。消费者数量最好等于分区数,或者略少于分区数。如果消费速度跟不上,优先考虑扩展分区而不是堆消费者。
3.5 一套可以直接抄的组合方案
结合上面的分析,整理一套我实测下来稳定且吞吐表现不错的参数组合:
| 位置 | 参数 | 值 | 说明 |
|---|---|---|---|
| Broker | num.io.threads | 16 | 磁盘IO并行度 |
| Broker | num.network.threads | 6 | 网络线程数 |
| Broker | num.replica.fetchers | 4 | 副本拉取并行度 |
| Broker | socket.send.buffer.bytes | 1024000 | Socket发送缓冲 |
| Broker | socket.receive.buffer.bytes | 1024000 | Socket接收缓冲 |
| Producer | batch.size | 65536 | 批量大小64KB |
| Producer | linger.ms | 10 | 等待积攒批次 |
| Producer | compression.type | lz4 | 启用压缩 |
| Producer | acks | all | 高可靠性 |
| Consumer | fetch.min.bytes | 4096 | 攒批拉取 |
| Consumer | fetch.max.wait.ms | 500 | 最大等待时间 |
| Consumer | enable.auto.commit | false | 手动提交 |
这套配置在我的3节点16C/32G环境下,单Topic双分区压测,生产吞吐稳定在每秒30万条左右(1000字节消息),P99延迟控制在80ms以内。当然每套环境不一样,务必以压测数据为准。
4. 压测与瓶颈定位:性能只能说,能测才算数
4.1 官方压测工具用法
Kafka自带的性能测试脚本是调优必备,一个是kafka-producer-perf-test.sh,一个是kafka-consumer-perf-test.sh。Kafka 3.x版本里,消费压测脚本已经独立出来了,不要再用老文章里的kafka-consumer-perf-test参数,版本不同命令差别很大。
生产压测我用的命令:
bin/kafka-producer-perf-test.sh \ --topic perf-test \ --num-records 5000000 \ --record-size 1000 \ --throughput -1 \ --producer-props bootstrap.servers=10.0.0.11:9092,10.0.0.12:9092,10.0.0.13:9092 \ batch.size=65536 linger.ms=10 compression.type=lz4 acks=all--throughput -1表示不限制吞吐上限,跑出当前配置下的最大能力。--record-size 1000模拟1KB消息,这是最常见的数据体量。压测结束后脚本会输出每秒消息数、每秒数据量和延迟分位数,这三项是判断性能的核心指标。
消费压测命令:
bin/kafka-consumer-perf-test.sh \ --bootstrap-server 10.0.0.11:9092,10.0.0.12:9092,10.0.0.13:9092 \ --topic perf-test \ --messages 5000000 \ --group perf-consumer-group \ --threads 8这里注意,--threads是消费线程数,不是消费者组实例数。消费压测主要看消费吞吐是否跟得上生产吞吐,如果消费速率明显低于生产速率,就要检查消费者配置和网络带宽了。
4.2 读懂压测结果的三个关键点
第一个关键点是延迟分位数。脚本会输出50th, 95th, 99th百分位延迟,我通常会额外关注P999,因为实时流处理场景很难容忍偶发长尾延迟。P99在80ms以内算健康,如果P99远高于P50,说明存在明显的周期性问题,优先怀疑GC停顿或磁盘IO抖动。
第二个关键点是吞吐量有没有达到一个相对平稳的平台。如果你不断调大生产者的batch.size,吞吐一直在涨,说明还没到瓶颈;如果涨到某个值后开始回落,说明缓冲设置已经过度,增加了GC压力和内存占用,这时候要停下来找其他瓶颈。
第三个关键点是对比单分区和全分区的吞吐。先测--topic perf-test这个Topic只有1个分区,再测有12个分区的Topic,如果分区增加后吞吐没有相应提升,说明瓶颈在节点网络或CPU,而不是分区并行度。如果提升了,说明当前并行度还不够,可以继续增加分区。
4.3 性能瓶颈定位思路
压测不达标时,你要有一套系统的定位方法,而不是盲目改参数。
先用top看CPU。如果CPU使用率已经接近饱和,再看看是用户态还是内核态。用户态高大概率是压缩解压和GC开销,考虑降低压缩级别或者换压缩算法;内核态高大概率是网络和IO中断过多,考虑加大socket缓冲或者检查网卡多队列。
如果CPU不高但吞吐上不去,重点检查磁盘。用iostat -x 1看磁盘%util和await。如果磁盘长时间在95%以上,说明存储是瓶颈。Kafka是顺序写,SSD的连续写能力通常在1GB/s以上,如果达不到,看看是不是文件系统挂载参数有问题,或者数据目录跨盘分布不均匀。
如果磁盘和CPU都很健康,那就是网络瓶颈。用iperf3或者系统级的sar -n DEV 1看网卡流量是否接近带宽上限。我遇到过千兆网卡跑满导致吞吐上不去的情况,后来换了万兆网卡,问题直接消失。
这里再强调一下JVM的检查。压测过程要盯着GC日志,我通常开启-Xlog:gc*:gc.log并定期检查Full GC次数。如果Full GC频繁,说明堆太小或堆内对象异常;如果老年代持续增长,就要排查是否有未释放的缓存进入了堆。Kafka的JVM堆参数我建议用较小的固定值,6GB到8GB,剩下内存全部留给Page Cache。
5. 常见故障排查与日常运维实录
5.1 高频故障速查
| 现象 | 可能原因 | 排查解决 |
|---|---|---|
| 生产者报TimeoutException | ISR副本数不足,acks=all时等待副本超时 | 检查Broker是否有节点宕机,查看ISR列表是否只剩leader |
| 消费者反复rebalance | Session超时时间太短,消费处理耗时长于session.timeout.ms | 调大session.timeout.ms和max.poll.interval.ms |
| 跨节点连接失败 | advertised.listeners配置成localhost或错误IP | 检查advertised.listeners,确保客户端能访问该地址 |
| 磁盘空间不停涨 | log.retention.hours太大或Topic消息量大 | 用kafka-log-dirs查看目录分布,调整保留策略 |
| 分区leader频繁切换 | 磁盘IO抖动导致心跳超时 | 检查磁盘await,优化存储性能 |
| 部分分区不可用 | 磁盘满或Broker崩溃后未恢复 | df -h检查磁盘,查看server.log中详细报错 |
| 消费Lag持续增长 | 消费者吞吐跟不上生产吞吐,或分区数少于消费者数 | 增加分区数,优化消费者端fetch参数 |
5.2 两个印象深刻的排查记录
先说一个ISR频繁收缩的问题。某次压测刚开始,生产端频繁报NotLeaderOrFollowerException,我查看Topic的describe信息发现ISR列表不断变化。一开始怀疑是Broker负载太高,后来用iostat -x一看,复制数据时磁盘%util飙到100%,而数据盘用的是普通SATA盘,根本扛不住多个分区的复制IO。后来把数据盘换成NVMe SSD,ISR稳定了,同样配置下吞吐提升了一倍多。这件事让我坚定了一个原则:Kafka集群的数据盘必须用SSD,最好NVMe,磁盘性能决定吞吐下限。
另一个是生产者和消费者不在同一个网段的场景。Broker本身在同一内网,但生产客户端在另外一个网段,网络要经过三层设备。压测时吞吐始终上不去,用sar看到网络包量正常,但重传率异常高。排查后发现是跨网段通信时TCP窗口缩放有问题,Broker端的net.ipv4.tcp_window_scaling被关了。开启后吞吐恢复正常。网络参数有时比Kafka自身参数更影响吞吐,这是很多人忽略的盲区。
5.3 日常巡检的监控指标与命令
Kafka的JMX指标是判断集群健康度的第一手资料,重点关注这几项:
BytesInPerSec和BytesOutPerSec:流入流出速率,用来自定义告警阈值UnderReplicatedPartitions:小于副本因子的分区数,正常应为0IsrShrinksPerSec和IsrExpandsPerSec:ISR收缩扩张频率,频繁波动说明副本同步不稳定RequestHandlerAvgIdlePercent:请求处理线程空闲率,如果长期低于30%,说明IO线程吃紧LogFlushRateAndTimeMs:日志刷盘频率和时间,异常偏高说明磁盘IO有问题
不用特意搭一套监控系统的话,可以用JMX暴露方式配合现有监控。在kafka-server-start.sh里添加:
export JMX_PORT=9999然后用jconsole连接Broker IP的9999端口查看指标。生产上更推荐用Prometheus的JMX Exporter方式采集,但那是另一个话题了,这里不展开。
日常巡检命令也很重要:
# 查看Topic分区leader分布 bin/kafka-topics.sh --bootstrap-server 10.0.0.11:9092 --describe --topic perf-test # 查看消费者组Lag bin/kafka-consumer-groups.sh --bootstrap-server 10.0.0.11:9092 --describe --group my-group # 查看日志目录磁盘占用 bin/kafka-log-dirs.sh --bootstrap-server 10.0.0.11:9092 --describe --topic-list perf-test我一般每天定时跑一次消费者组Lag检查,任何Topic的Lag超过阈值就触发人工介入,这套机制帮我们提前发现了好几次潜在故障。
最后分享一个实际调试中的体会:如果你打算在高吞吐和可靠性之间找平衡,先把底层存储和网络做对,再谈参数调优;参数里最见效的往往是batch.size、linger.ms、compression.type和num.replica.fetchers这几个,但最终验证效果还是绕不过一遍又一遍的压测。另外,如果能给Kafka单独配一块NVMe SSD承载数据目录,这个决定会在你未来每一次压测和线上高峰中得到回报。