上一篇解决了安全体系的认证、授权与加密。这一篇进入生产运维。

生产运维容易被理解为"改配置 + 重启"。更准确的说法是:Kafka 集群的运维本质上是对分布式日志的分区所有权、副本状态和元数据的受控迁移,每一步操作都在改变哪些 broker 持有哪些 partition 的哪个角色。

本文只抓三个问题:如何安全地扩缩 broker,应该盯哪些 JMX 指标,以及遇到常见故障时从哪里开始排查。

集群扩缩容模型

Kafka 集群的 broker 状态可以用一个简化模型描述:

1
2
3
4
5
6
7
8
9
10
11
12
当前集群: [broker-0, broker-1, broker-2]
partition 分布:
topic-A-0 -> leader=0, follower=[1,2]
topic-A-1 -> leader=1, follower=[0,2]

加 broker-3 后:
broker-3 加入集群,无 partition
-> 手动触发 partition reassignment
topic-A-0 -> leader=0, follower=[1,3] (broker-2 释放一个 follower)

缩容 broker-2:
先迁走所有 partition -> 再关闭进程

新 broker 加入集群后不会自动承接流量,必须显式执行分区重分配。Kafka 提供 kafka-reassign-partitions.sh 工具完成这个过程。

扩容:添加新 Broker

添加 broker 分两步:启动进程,然后触发 partition 迁移。

生成重分配计划

先准备一个 JSON 文件描述要重分配的 topic:

1
{"topics": [{"topic": "my-topic"}], "version": 1}

生成计划:

1
2
3
4
5
kafka-reassign-partitions.sh \
--bootstrap-server localhost:9092 \
--topics-to-move-json-file topics.json \
--broker-list "0,1,2,3" \
--generate

输出两段 JSON:当前分配(可用于回滚)和建议的新分配。将建议方案保存为 reassignment.json

执行重分配

1
2
3
4
kafka-reassign-partitions.sh \
--bootstrap-server localhost:9092 \
--reassignment-json-file reassignment.json \
--execute

Kafka 在后台启动副本同步,新 follower 追赶 leader 的 log end offset,追上后加入 ISR,最后 leader 切换(如果计划包含 leader 迁移)。

监控进度

1
2
3
4
kafka-reassign-partitions.sh \
--bootstrap-server localhost:9092 \
--reassignment-json-file reassignment.json \
--verify

重分配期间集群网络流量会显著上升,因为副本同步需要跨 broker 传输数据。可通过 throttle 参数限速:

1
2
3
kafka-reassign-partitions.sh \
--execute \
--throttle 50000000 # 50 MB/s

Preferred Leader 选举

重分配完成后,partition 的实际 leader 不一定是 preferred replica(replica 列表第一个)。执行 leader 平衡:

1
2
3
4
kafka-leader-election.sh \
--bootstrap-server localhost:9092 \
--election-type PREFERRED \
--all-topic-partitions

auto.leader.rebalance.enable=true 默认开启后台自动平衡(默认每 300 秒检查一次),但在高流量场景下可能引起短暂的 leader 切换延迟,部分团队选择关闭它,改为手动触发。

缩容:安全移除 Broker

缩容必须先把 broker 上的所有 partition 迁走,再关闭进程。直接关闭进程会触发 leader 选举风暴,影响所有在该 broker 上有 leader 的 partition。

安全缩容步骤:

  1. 获取 broker 当前持有的所有 partition:kafka-topics.sh --describe 过滤出目标 broker
  2. 生成迁离计划(--broker-list 不包含目标 broker)
  3. 执行重分配,等待 verify 确认完成
  4. 关闭目标 broker 进程

Kafka 3.x 在 KRaft 模式下,broker 退役可以通过 kafka-remove-brokers.sh 脚本半自动化完成,自动生成迁离计划并等待完成。

关键监控指标

Kafka 通过 JMX 暴露内部状态。以下指标反映集群健康状况的关键维度。

UnderReplicatedPartitions

MBean:kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions

含义:ISR 副本数少于 replication.factor 的 partition 数量。任何非 0 值都意味着数据冗余度下降,需要立即排查。

常见原因:broker 宕机、网络分区、副本同步落后超过 replica.lag.time.max.ms(默认 30000ms)。

ActiveControllerCount

MBean:kafka.controller:type=KafkaController,name=ActiveControllerCount

含义:集群中活跃 Controller 的数量。正常值为 1,0 表示无 Controller(集群不可写),大于 1 说明 Controller 选举出现脑裂(极少见,通常伴随 ZooKeeper 或 KRaft 异常)。

OfflinePartitionsCount

MBean:kafka.controller:type=KafkaController,name=OfflinePartitionsCount

含义:没有可用 leader 的 partition 数量。此值非 0 时对应 partition 完全不可用(生产者报 LeaderNotAvailableException,消费者无法拉取)。

RequestHandlerAvgIdlePercent

MBean:kafka.server:type=KafkaRequestHandlerPool,name=RequestHandlerAvgIdlePercent

含义:请求处理线程的平均空闲比例。低于 0.3(30%)说明 broker CPU 或 I/O 成为瓶颈,请求队列开始积压,端到端延迟上升。

NetworkProcessorAvgIdlePercent

MBean:kafka.network:type=SocketServer,name=NetworkProcessorAvgIdlePercent

含义:网络层线程的平均空闲比例。低于 0.3 说明网络连接处理成为瓶颈,通常表现为客户端连接慢或请求超时。

BytesInPerSec / BytesOutPerSec

MBean:kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec

含义:流入和流出的字节速率。BytesOutPerSec 通常是 BytesInPerSec 的数倍(每个 consumer group 各读一份,加上副本复制流量)。突然下降说明生产者或消费者连接中断;突然上升需排查是否有未预期的流量进入。

Consumer Lag

Consumer lag 不在 broker JMX 里,通过命令行或 Prometheus exporter 观察:

1
2
3
4
kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 \
--describe \
--group my-consumer-group

输出中的 LAG 列是当前 consumer offset 与 partition log end offset 的差值。持续增长的 lag 说明消费速度跟不上生产速度。

可运行实验

本节实验在单机 Docker Compose 环境(三个 Kafka broker)下可完整复现。

实验一:观察 Consumer Lag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 启动一个慢消费者(sleep 模拟处理耗时)
kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic test-topic \
--group slow-group &

# 快速生产消息
for i in $(seq 1 1000); do
echo "msg-$i" | kafka-console-producer.sh \
--bootstrap-server localhost:9092 \
--topic test-topic
done

# 观察 lag
kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 \
--describe \
--group slow-group

LAG 列的数值反映了生产速度与消费速度的差距。

实验二:触发 UnderReplicatedPartitions

1
2
3
4
5
6
7
8
9
10
# 停止 broker-2
docker stop kafka-2

# 观察 UnderReplicatedPartitions
# 通过 JConsole 连接 broker-0:9999,查看 MBean 值
# 或通过 kafka-log-dirs.sh 观察副本状态
kafka-log-dirs.sh \
--bootstrap-server localhost:9092 \
--topic-list test-topic \
--describe

实验三:执行 partition 重分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 生成计划
echo '{"topics":[{"topic":"test-topic"}],"version":1}' > topics.json
kafka-reassign-partitions.sh \
--bootstrap-server localhost:9092 \
--topics-to-move-json-file topics.json \
--broker-list "0,1,2" \
--generate > plan.json

# 取出 proposed plan 部分(第二段 JSON)执行
kafka-reassign-partitions.sh \
--bootstrap-server localhost:9092 \
--reassignment-json-file reassignment.json \
--execute

# 等待完成
kafka-reassign-partitions.sh \
--bootstrap-server localhost:9092 \
--reassignment-json-file reassignment.json \
--verify

实验四:JMX 指标采集

Kafka broker 默认在 JMX_PORT(通常 9999)暴露 JMX。使用 Prometheus JMX Exporter 可以把 JMX 指标转换为 Prometheus 格式:

1
2
3
4
5
6
# jmx_exporter_config.yaml 片段
rules:
- pattern: "kafka.server<type=ReplicaManager, name=UnderReplicatedPartitions><>Value"
name: kafka_server_replicamanager_underreplicatedpartitions
- pattern: "kafka.controller<type=KafkaController, name=ActiveControllerCount><>Value"
name: kafka_controller_kafkacontroller_activecontrollercount

启动后 Grafana 可以直接从 /metrics 端点拉取。

故障排查流程

ISR 收缩定位

ISR 收缩(UnderReplicatedPartitions > 0)的排查顺序:

  1. 确认是哪些 partition 收缩:kafka-topics.sh --describe --under-replicated-partitions
  2. 找到落后的 follower broker:对比 LogEndOffsetHighWatermark
  3. 检查该 broker 的 GC 日志(/var/log/kafka/kafka-gc.log)和 server 日志(/var/log/kafka/server.log),寻找 Full GC 或 IOException
  4. 检查磁盘 I/O:iostat -x 1 观察 %util 是否接近 100%
  5. 如果是网络问题:sar -n DEV 1netstat -s 查看丢包率

Consumer Lag 积压排查

  1. 确认哪个 partition 的 lag 最大
  2. 检查对应消费者线程是否存活:消费者日志中有无 poll() 超时或 coordinator 重连
  3. 检查下游处理是否阻塞:消费者的业务代码(数据库写入、RPC 调用)是否有慢操作
  4. 如果消费者正常但 lag 持续增长:说明消费速度不足,需要扩展消费者实例(增加 consumer group 成员,前提是 partition 数量足够)

Leader 选举风暴

大量 partition 的 leader 同时切换(ActiveControllerCount 短暂为 0,然后大量 leader 变更事件)的常见原因:

  • Controller 所在 broker 宕机:新 Controller 选举期间(通常数秒内)所有 leader 选举暂停
  • ZooKeeper session 超时(ZooKeeper 模式):zookeeper.session.timeout.ms 过小,网络抖动导致频繁重选
  • broker 进程 Full GC 时间超过心跳超时:调整 JVM 堆大小或 GC 策略(G1GC 通常比 CMS 更适合 Kafka broker)

KRaft 模式下 Controller 是 Raft quorum 中的角色,选举速度比 ZooKeeper 模式快(通常在 1 秒以内),但 quorum 中超过半数节点不可用时集群同样不可写。

OOM 与 GC 压力

Kafka broker 的堆内存默认 1GB(KAFKA_HEAP_OPTS=-Xmx1G)。生产环境建议:

  • 堆:4–8GB,不超过 32GB(超过 32GB 会失去 JVM 的指针压缩优化)
  • 页缓存:剩余物理内存留给操作系统页缓存(Kafka 的顺序读写性能高度依赖页缓存)
  • GC 策略:G1GC,-XX:MaxGCPauseMillis=20

OOM 触发时 broker 进程崩溃,UnderReplicatedPartitions 立刻上升。通过 -XX:+HeapDumpOnOutOfMemoryError 开启堆转储,事后用 MAT 分析泄漏点。

模式提炼

Kafka 运维的核心模式是"可观测性优先,操作不可逆性控制":

  • 每个运维动作(分区迁移、broker 下线)都有对应的中间状态可以观测(--verify、JMX 指标)
  • 缩容和迁移必须先完成数据迁移再做进程操作,不能靠 Kafka 自动恢复兜底
  • 监控指标的告警阈值比操作手册更重要——知道"什么时候需要介入"比"介入后怎么操作"更难

这个模式在其他分布式存储系统(Elasticsearch 节点下线、HBase Region 迁移)中同样成立:数据局部性的改变必须显式完成,而不是依赖自动平衡。

工程迁移表

Kafka 概念 工程等价
partition reassignment 数据库分片迁移(resharding),需要双写或只读期
UnderReplicatedPartitions RAID 阵列降级(有磁盘掉线,数据冗余度下降)
preferred leader election 主节点回切(failback),把流量迁回原主
consumer lag 队列积压深度,等同于 MySQL slave 的 relay log 积压
GC pause → ISR 收缩 JVM STW 期间心跳超时,等同于数据库锁等待超时导致连接断开
JMX exporter + Prometheus APM 的基础设施层指标采集,等同于 Micrometer + Prometheus 的应用层指标

常见误解

误解一:新 broker 加入后流量会自动均衡

新 broker 加入集群后,已有 topic 的 partition 不会自动迁移到新 broker。Kafka 只会在创建新 topic 时把新 broker 纳入分配候选。扩容后必须手动执行 partition reassignment 才能让新 broker 分担负载。

误解二:auto.leader.rebalance.enable=true 是安全的

默认开启的 leader 自动平衡会在后台周期性地把 leader 迁回 preferred replica。在高流量时段这个迁移会引起短暂的 leader 不可用(通常在 10–100ms 量级),但如果集群本身不稳定(频繁 ISR 变更),自动平衡可能加剧抖动。建议评估是否在业务低峰期手动触发。

误解三:监控 consumer lag 就够了

Consumer lag 是结果指标,只告诉消费速度落后了,不告诉原因。配合 RequestHandlerAvgIdlePercent(broker 侧 CPU 压力)、GC 日志(JVM 停顿)、消费者应用日志(业务处理耗时)才能定位根因。单独盯 lag 会延误排查时间。

误解四:缩容直接关进程即可

直接关闭 broker 进程会触发该 broker 上所有 leader partition 的重新选举,ISR 中的其他副本需要竞选新 leader。在 partition 数量多(数千个)的集群里,这个过程需要数十秒,期间对应 partition 的生产者会收到 NotLeaderOrFollowerException。安全做法是先迁走所有 partition 再关闭进程。

练习

  1. 启动一个三 broker 的 Docker Compose Kafka 集群,创建 replication factor 为 3 的 topic,停掉一个 broker,观察 UnderReplicatedPartitions 的变化,然后重启该 broker,观察 ISR 恢复过程。

  2. 向已有集群添加第四个 broker,使用 kafka-reassign-partitions.sh 把部分 partition 迁移到新 broker,用 --verify 监控迁移进度,完成后执行 preferred leader election,对比迁移前后各 broker 的 BytesInPerSec

  3. 配置一个消费速度故意慢于生产速度的消费者(在 poll() 后 sleep 500ms),用 kafka-consumer-groups.sh --describe 观察 lag 随时间的增长,然后启动第二个 consumer 实例加入同一 group,观察 lag 是否下降。

  4. 下载 Prometheus JMX Exporter,配置 Kafka broker 以 JMX Agent 模式启动,用 Prometheus + Grafana 搭建一个只包含本文六个关键指标的 dashboard,停掉一个 broker,观察 dashboard 上哪些指标率先变化。

系列导航

序号 主题
00 导读:为什么 Kafka 的核心是一根日志
01 架构总览:Broker、Controller 与元数据管理
02 日志存储:Segment、Index 与零拷贝
03 Producer 内部机制:攒批、分区与 acks
04 幂等 Producer 与序列号:消息不重不丢的第一层
05 Consumer Group 协议:分配、重平衡与静态成员
06 Offset 管理:提交、重置与消费语义
07 副本与 ISR:高可用的代价和折中
08 Controller 与 KRaft:从 ZooKeeper 到内置共识
09 Exactly-Once 与事务:跨 partition 的原子写入
10 日志压缩:把 topic 当 KV 表用
11 Kafka Streams:在日志之上构建流处理
12 Kafka Connect:标准化的数据管道
13 Schema Registry 与数据治理:给消息加上契约
14 性能模型:吞吐、延迟与调优思路
15 安全体系:认证、授权与加密
16 生产运维:集群扩缩、监控指标与故障排查
17 Kafka 与 RocketMQ:两种消息系统的设计选择

参考资料