Kafka 扩容后新 Broker 空转 3 小时:加机器不是扩容,重分配才是

引言

去年双十一前扩容,我们从 3 台 Broker 扩到 6 台。

机器加完,监控上看新 Broker:CPU 2%,磁盘 IO 为零,网络流量只有心跳。3 小时后有人问了一句:"新机器是不是没加入集群?"

查了一圈,集群成员确实是 6 台,但 kafka-topics.sh --describe 里所有分区的 Replicas 还是老三台的 ID:

Topic: order-events  Partition: 0  Leader: 1  Replicas: 1,2,3  Isr: 1,2,3
Topic: order-events  Partition: 1  Leader: 2  Replicas: 2,3,1  Isr: 2,3,1
...所有分区,一个新 Broker 都没有

Kafka 不会自动把分区搬到新 Broker。 加机器只是让集群多了一个可用节点,要把负载真正分过去,必须做分区重分配——这一步没人做,新机器就是白买的。

同一次扩容还差点出第二个事故:重分配方案生成后直接 --execute,没加 --throttle。跨机房带宽瞬间打满,在线消费的 P99 从 15ms 飙到 800ms,告警群已经开始响了,我们手动停掉命令才压住。

还有一次缩容,同事图省事直接 kill -9 下线一台 Broker,瞬间触发 200 多个分区的 Leader 切换,UnderReplicatedPartitions 冲到 300 多,持续了 7 分钟。

三次教训指向同一件事:Kafka 的高可用运维,难的不是命令,是顺序——先算什么、再动什么、每一步用什么验证、出事怎么退。 先放全流程总表:

阶段核心动作关键命令验证信号
扩容前算容量、算带宽、确认版本—磁盘/网络/分区数三笔账
加 Broker配置、格式化、启动、注册kafka-storage.sh、kafka-broker-api-versions.sh新节点出现在集群成员里
重分配生成、审阅、限流执行、验证kafka-reassign-partitions.sh分区数均衡、URP 回落
扩分区评估顺序性后扩容kafka-topics.sh --alter --partitions新分区有流量、无乱序
缩容先迁移、再优雅停机重分配 + controlled.shutdown目标 Broker 副本数归零
监控指标、分级、去重、值班手册JMX + kafka-exporter + AlertmanagerURP/Offline/ISR/磁盘

一句话破题:扩容 = 加节点 + 重分配 + 验证三件事,缺一件新机器就空转;缩容 = 先搬家再拆房,顺序反了就是事故;而监控不是扩容之后的收尾,是整个过程的安全带——没有 URP 和 OfflinePartitions 的实时看板,你根本不知道自己走到了哪一步。


一、动手之前:先算三笔账

1.1 第一笔账:你到底缺什么

"集群扛不住了"这句话太模糊,先拆成三种诉求,动作完全不同:

诉求判断依据该做什么
吞吐不够Broker CPU 高、RequestHandler 空闲率低、生产延迟上升扩 Broker + 重分配,或扩分区
容量不够磁盘水位 >70%、retention 被迫缩短扩 Broker + 重分配(分摊分区)
容灾不够同一分区的 3 个副本都在同一机架/同一可用区扩到新机架 + 重分配时保证跨机架
单机风险某台 Broker 分区数远超其他重分配做均衡,不一定要加机器

最常见的误判是"吞吐不够就加机器"。如果瓶颈是分区数(消费者并行度上不去、单分区写入打满),加 Broker 不解决问题——因为分区不会自动变多,新 Broker 分不到活。先看瓶颈在分区还是节点,再决定加什么。

1.2 第二笔账:容量与带宽

磁盘需求 ≈ 峰值消息字节/秒 × retention 秒数 × 副本数 × 1.3(余量与索引)
网络需求 ≈ 峰值生产流量 × 副本数(写放大)+ 重分配期间的迁移流量
分区需求 ≈ 峰值 TPS ÷ 单分区安全处理吞吐

举个具体的:峰值 20MB/s 写入、保留 3 天、RF=3:

20MB/s × 86400 × 3 × 3 ≈ 15.5TB(裸容量)
加 30% 余量 ≈ 20TB,摊到 6 台 Broker,每台约 3.4TB

重分配的迁移时间也要提前算:

需要迁移的数据量 ÷ throttle(每 Broker)
例:单 Broker 迁移 500GB,限流 100MB/s → 约 85 分钟

如果迁移时间超过业务低峰窗口,就把 throttle 调大,或者拆成两晚做。 不要在执行到一半时才发现天亮了大促。

1.3 第三笔账:顺序性与版本

风险触发动作后果规避
key 的 hash 漂移扩分区同 key 新消息进新分区,顺序乱顺序敏感 Topic 不在线扩分区
副本跨机架丢失重分配方案没审机房故障仍有丢副本风险审阅 Replicas 的机架分布
迁移带宽挤占重分配没限流在线 P99 飙升--throttle 必设
副本数被改小手改重分配 JSON低于 min.insync.replicas 直接拒写执行前核对 RF 与 min ISR

版本差异也要先确认,命令和监控指标都不一样:

项目ZooKeeper 模式(≤3.x 可选)KRaft 模式(3.3+ 生产可用,4.0 起唯一)
集群元数据ZKController Quorum
新 Broker 加入启动即注册到 ZK必须用同一个 cluster.id 格式化
Controller 指标ActiveControllerCountkafka-metadata-quorum.sh
扩容命令基本一致基本一致,--bootstrap-server 指向 Broker

本文命令以 Kafka 3.7+ / KRaft 为准,ZK 模式的差异单独标注。


二、加 Broker:从配置到注册成功

2.1 配置清单

新 Broker 的 server.properties,五个字段必须逐项核对:

# 身份:集群内唯一,不能和现有 Broker 冲突
node.id=6
process.roles=broker

# 机架:容灾的基础,必须和现有 Broker 的 rack 规划一致
broker.rack=rack-b

# 网络:listeners 是绑定地址,advertised 是给客户端看的地址
listeners=PLAINTEXT://10.0.0.16:9092
advertised.listeners=PLAINTEXT://10.0.0.16:9092

# 数据目录:独立磁盘,不要和系统盘共用
log.dirs=/data/kafka-logs

# 必须和现有集群完全一致,否则注册被拒
controller.quorum.voters=1@10.0.0.11:9093,2@10.0.0.12:9093,3@10.0.0.13:9093

三个高频翻车点:

  1. node.id 重复:新 Broker 起不来,日志报 Duplicate node.id。
  2. controller.quorum.voters 写错:Broker 能启动,但一直连不上 Controller,表现为"进程活着、集群里没有"。
  3. advertised.listeners 写成内网 IP 之外的地址:Broker 注册成功,但客户端连不上,元数据里能看到它、流量进不来。

2.2 格式化:必须用现有集群的 cluster.id

KRaft 模式下,新 Broker 的数据目录必须先格式化,而且必须用现有集群的 cluster.id,不能重新生成一个:

# 从现有 Broker 上取 cluster.id(两种方式任选)
kafka-metadata-quorum.sh --bootstrap-server kafka:9092 describe --status | grep ClusterId
# 或直接看现有 Broker 的 meta.properties
cat /data/kafka-logs/meta.properties | grep cluster.id

# 用这个 id 格式化新节点
kafka-storage.sh format -t <existing-cluster-id> -c /opt/kafka/config/server.properties

# 确认格式化结果里的 cluster.id 一致
kafka-storage.sh info -c /opt/kafka/config/server.properties

如果这里用 random-uuid 重新生成,新 Broker 会因为 cluster.id 不匹配被拒绝加入,日志里是 Cluster ID mismatch。这是 KRaft 扩容最常见的第一个坑。

2.3 启动与验证:三个信号缺一不可

# 启动
kafka-server-start.sh -daemon /opt/kafka/config/server.properties

# 信号一:进程与日志
tail -f /data/kafka-logs/server.log | grep -E "started|Registered|ERROR"

# 信号二:Broker 出现在集群成员列表里
kafka-broker-api-versions.sh --bootstrap-server kafka:9092 | grep 10.0.0.16

# 信号三:Controller 视角下节点已注册
kafka-metadata-quorum.sh --bootstrap-server kafka:9092 describe --status

三个信号都齐了,才能进入下一步。只看到进程活着不算成功——"进程活着但没注册"和"注册了但客户端连不上"是两种完全不同的故障,验证要分开做。

2.4 新 Broker 此时仍然是"空转"的

注册成功只代表它加入了集群,不代表它承担了任何分区。kafka-topics.sh --describe 里依然看不到它:

Topic: order-events  Partition: 0  Leader: 1  Replicas: 1,2,3  Isr: 1,2,3
                                                              ↑ 没有 6

这是正常的。Kafka 的分配是"粘性"的:已有分区不会因为新节点加入就自动迁移,否则每加一台机器都要全集群搬一次数据。把分区搬过去,是下一章的重分配要做的事。


三、分区重分配:扩缩容的核心动作

3.1 重分配到底在做什么

kafka-reassign-partitions.sh 做三件事:

  1. 计算一份新的"分区 → 副本列表"映射
  2. 让相关 Broker 按新映射复制数据(新增副本从 Leader 拉取)
  3. 复制完成后切换副本集合,多余的副本被删除
重分配前:P0  Replicas: 1,2,3
重分配后:P0  Replicas: 1,2,6

过程:
  Broker 6 从 Leader(Broker 1)拉取 P0 的全量数据
  → 追上 LEO,进入 ISR
  → Controller 更新元数据:Replicas 变为 1,2,6
  → Broker 3 上的副本被标记删除

数据是"先复制、后切换、再删除",所以重分配过程中集群的副本数是暂时变多的,磁盘会短时上涨。 容量规划时要把这部分余量算进去。

3.2 生成方案

先写要迁移的 Topic 清单:

cat > /tmp/topics-to-move.json <<'EOF'
{
  "topics": [
    {"topic": "order-events"},
    {"topic": "order-paid"},
    {"topic": "inventory-events"}
  ],
  "version": 1
}
EOF

生成方案,目标 Broker 列表是 1–6:

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

输出是两段 JSON:第一段是当前分配,第二段是建议分配。把第二段单独保存:

# 把 "Proposed partition reassignment configuration" 之后的那段 JSON
# 保存为 /tmp/reassignment.json

建议 JSON 的结构:

{
  "version": 1,
  "partitions": [
    {"topic": "order-events", "partition": 0,
     "replicas": [1, 2, 6], "log_dirs": ["any", "any", "any"]},
    {"topic": "order-events", "partition": 1,
     "replicas": [4, 5, 6], "log_dirs": ["any", "any", "any"]}
  ]
}

3.3 审阅:工具给的方案不能直接用

--generate 只保证"尽量均衡",不保证你的业务约束。执行前必须人工核对四件事:

检查项怎么查不合格的表现
副本数每条 replicas 的长度少于 RF=3,或低于 min.insync.replicas
机架分布对照 broker.rack 映射同一分区的 3 个副本全在同一机架
单 Broker 分区数统计各 Broker 出现次数某台 800 个、某台 200 个
Leader 倾斜执行后看 LeaderCount新 Broker 一个 Leader 都没有

一个实用的检查脚本(统计建议方案里每台 Broker 分到多少个副本):

python3 - <<'PY'
import json, collections
plan = json.load(open('/tmp/reassignment.json'))
counts = collections.Counter()
for p in plan['partitions']:
    for r in p['replicas']:
        counts[r] += 1
for broker, n in sorted(counts.items()):
    print(f"broker {broker}: {n} replicas")
PY

六台 Broker 的理想结果应该是每台 300–400 个副本(按分区总数 3 副本折算)。如果某台超过均值 50%,把方案改一改再执行——重分配的代价是搬数据,搬错一次要再搬一次。

3.4 执行:throttle 是必选项,不是可选项

kafka-reassign-partitions.sh --bootstrap-server kafka:9092 \
  --reassignment-json-file /tmp/reassignment.json \
  --throttle 104857600 \
  --execute

--throttle 的单位是 字节/秒,按 Broker 计。104857600 = 100MB/s。执行期间工具会自动给相关 Broker 设置:

leader.replication.throttled.rate=104857600
follower.replication.throttled.rate=104857600

执行结束后自动清除。不设 throttle 会发生什么,引言里已经演过了:跨机房带宽被复制流量吃满,在线消费 P99 从 15ms 飙到 800ms。

限流值怎么定:

场景建议值理由
同机房,万兆网络200–500MB/s带宽富余,快速搬完
跨机房/跨可用区50–100MB/s给在线流量留余量
业务高峰期20–50MB/s宁可慢,不可抖
磁盘 IO 紧张先解决磁盘,再重分配复制和在线读写抢 IO

3.5 验证:看命令输出,也看监控

# 命令侧
kafka-reassign-partitions.sh --bootstrap-server kafka:9092 \
  --reassignment-json-file /tmp/reassignment.json --verify

输出会逐分区显示 completed 或 in progress。全部 completed 之后,再用三个信号确认:

# 信号一:新 Broker 出现在 Replicas 里
kafka-topics.sh --bootstrap-server kafka:9092 --describe --topic order-events

# 信号二:URP 回落到 0
# 监控面板或 JMX:
# kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions

# 信号三:各 Broker 分区数与 Leader 数均衡
kafka-topics.sh --bootstrap-server kafka:9092 --describe | \
  awk '/Replicas:/{for(i=1;i<=NF;i++) if($i=="Replicas:"){split($(i+1),a,","); for(k in a) c[a[k]]++}} END{for(b in c) print b, c[b]}'

重分配期间 URP 短时上升是正常的——新副本正在追赶 Leader,ISR 还没齐。判断标准是"趋势":URP 应该随时间下降,而不是持续上涨。持续 15 分钟不降,说明复制跟不上(带宽不够或磁盘太慢),要么调大 throttle,要么先停下来排查。

3.6 中途断了怎么办

重分配是幂等的。命令被中断、机器重启、甚至你手动 Ctrl-C,已经完成的迁移不会回滚,重新执行同一份 JSON 即可从当前进度继续:

kafka-reassign-partitions.sh --bootstrap-server kafka:9092 \
  --reassignment-json-file /tmp/reassignment.json \
  --throttle 104857600 --execute

不要重新 --generate 一份新方案再执行——那会让已经搬好的数据再搬一次,白白多一轮流量。

3.7 自动化选项:Cruise Control

手工重分配的问题在于:人只能看到"现在均不均衡",看不到"未来会不会倾斜"。LinkedIn 开源的 Cruise Control 做三件事:

  • 持续采集 Broker/分区/副本指标,做异常检测
  • 给出重分配建议(含机架感知、副本分布、磁盘均衡)
  • 可配置自动执行,带 throttle 和 dry-run

适合集群规模大(几十台以上)、Topic 多、人工维护不过来的场景。小集群(3–6 台)手工做一次半小时,没必要引入额外组件。


四、扩分区:加分区不等于扩容

4.1 两个铁律

kafka-topics.sh --bootstrap-server kafka:9092 \
  --alter --topic order-events --partitions 48

第一条:只能加,不能减。 --alter --partitions 只接受比当前大的值,想缩回去只能重建 Topic。所以扩之前问一句:这个数字三年后还认吗?

第二条:存量数据不会迁移。 从 24 扩到 48,老消息原地不动,新消息按 hash(key) % 48 分布。这意味着:

扩容前:hash("order-89757") % 24 = 5   → P5,历史消息都在 P5
扩容后:hash("order-89757") % 48 = 17  → 新消息进 P17

同一个订单:老消息在 P5 排队,新消息在 P17 被另一个消费者处理
→ 顺序错乱

顺序敏感的业务,不要在线扩分区。 正确姿势是《Kafka 顺序消息实战》2.4 的三选一:规划一步到位、停写扩容、新旧分代。扩分区不是"免费加吞吐",它的价格是顺序性。

4.2 什么时候该扩分区

场景该扩分区吗原因
消费者并行度受分区数限制✅分区数 = 消费并行度上限
单分区写入打满✅拆细热点
只是 Broker CPU 高❌扩 Broker + 重分配更直接
顺序敏感 Topic 在线扩容❌hash 漂移打乱顺序
分区已经几百个⚠先评估 Controller/元数据压力

4.3 扩 Broker 和扩分区,别搞混

动作改变什么数据迁移生效方式
加 Broker集群节点数需重分配才迁移分钟级,需人工触发
重分配分区在 Broker 间的分布复制数据分钟到小时
扩分区分区总数(并行度上限)不迁移立即,但新分区只接新消息

一句话:扩 Broker 解决"每台机器太累",扩分区解决"并行度不够",重分配解决"活没分匀"。


五、缩容:先搬家,再拆房

5.1 正确顺序

① 生成不含目标 Broker 的重分配方案
② 执行 + 验证,确认目标 Broker 上副本数为 0
③ 优雅停机(controlled shutdown)
④ 观察 URP / OfflinePartitions / Leader 数
⑤ 下线机器

5.2 第一步:把分区搬走

假设要下线 Broker 6,目标列表只写 1–5:

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

保存建议方案后执行、验证,然后用 kafka-topics.sh --describe 确认没有任何分区的 Replicas 里还有 6:

kafka-topics.sh --bootstrap-server kafka:9092 --describe | grep -E "Replicas:.*\b6\b"
# 输出为空才安全

5.3 第二步:优雅停机

# server.properties(默认就是 true,确认别被改过)
controlled.shutdown.enable=true
controlled.shutdown.max.retries=3
controlled.shutdown.retry.backoff.ms=1000
# 用脚本停,不要 kill -9
kafka-server-stop.sh
# 或发送 SIGTERM,让 Broker 走完 controlled shutdown
kill -TERM <pid>

controlled shutdown 做的是:先把它作为 Leader 的分区领导权交给其他副本,再停止对外服务。这样客户端只会看到短暂的 Leader 切换,不会出现"分区无 Leader"。

直接 kill -9 的后果:Leader 无法交接,Controller 要等 replica.lag.time.max.ms(默认 30 秒)后才判定该 Broker 下线并重新选主。这 30 秒里,它领导的分区全部不可用——引言里 URP 冲到 300 的事故就是这么来的。

5.4 第三步:验证与观察

信号正常异常
URP30 秒内回落到 0持续 >0 超过 5 分钟
OfflinePartitionsCount0>0 立即 P0
LeaderCount其他 Broker 均匀接管某台骤增,可能过载
客户端延迟短暂抖动后恢复持续升高

5.5 缩容后必须核对副本数

缩容最容易出的隐性问题是副本数被"缩"掉了。如果重分配方案里手动改过 replicas,或者下线过程中有副本没迁完,可能出现某些分区只剩 2 个副本。此时 min.insync.replicas=2 还能写,但容灾能力已经下降——再坏一台就拒写。

# 核对每个分区的副本数是否仍为 3
kafka-topics.sh --bootstrap-server kafka:9092 --describe | \
  awk '/Replicas:/{n=split($0,a,"Replicas: "); split(a[2],b," "); split(b[1],c,","); if(length(c)<3) print $0}'

六、监控告警:整个过程的安全带

6.1 六个必须盯的指标

指标JMX(以你的版本为准)含义异常阈值
URPkafka.server:type=ReplicaManager,name=UnderReplicatedPartitions副本数不足的分区数>0 持续 5 分钟
离线分区kafka.controller:type=KafkaController,name=OfflinePartitionsCount没有 Leader 的分区数>0 立即告警
ActiveControllerkafka.controller:type=KafkaController,name=ActiveControllerCountZK 模式下应恒为 1≠1
ISR 收缩kafka.server:type=ReplicaManager,name=IsrShrinksPerSec副本掉出 ISR 的速率持续 >0
请求处理空闲率kafka.server:type=KafkaRequestHandlerPool,name=RequestHandlerAvgIdlePercentBroker 是否过载<30%
磁盘水位node_exporter数据盘使用率>80%

KRaft 模式下没有 ActiveControllerCount 这个 ZK 时代的指标,改用:

kafka-metadata-quorum.sh --bootstrap-server kafka:9092 describe --status
# 看 LeaderId、CurrentVoters、MaxFollowerLag 是否正常

6.2 告警分级与收敛

按团队告警规范落地成表:

告警表达式/条件级别通知策略
OfflinePartitions > 0sum(...offlinepartitionscount) > 0P0立即电话
Broker 掉线absent(...partitioncount{instance=...})P0立即电话
URP 持续 >0sum(...underreplicatedpartitions) > 0,for: 5mP1工作时间
URP 持续上升5 分钟内增长 >20%P0立即电话
ISR 持续收缩rate(...isrshrinks) > 0 持续 10mP1工作时间
请求处理空闲率低< 0.3 持续 5mP1工作时间
磁盘 >80%node_exporterP2早晨汇总
磁盘 >90%node_exporterP1工作时间

三条纪律(和《Kafka 监控告警实战》里的一致):

  1. P0 立即通知,P1 工作时间处理,P2 凌晨延迟到早晨
  2. 同一告警 5 分钟内只通知一次,靠 repeat_interval: 5m 收敛
  3. Alertmanager 的时间区间默认按 UTC 解析,必须显式配置时区,否则"凌晨延迟"策略会整体错位 8 小时

6.3 Prometheus + kafka-exporter 配置

# prometheus.yml
scrape_configs:
  - job_name: kafka
    scrape_interval: 15s
    static_configs:
      - targets:
          - '10.0.0.11:9308'
          - '10.0.0.12:9308'
          - '10.0.0.13:9308'
          - '10.0.0.16:9308'
# alert-rules.yml
groups:
  - name: kafka-ha
    rules:
      - alert: KafkaOfflinePartitions
        expr: sum(kafka_controller_kafkacontroller_offlinepartitionscount) > 0
        for: 1m
        labels:
          severity: P0
        annotations:
          summary: "Kafka 存在无 Leader 分区,服务已部分不可用"

      - alert: KafkaUnderReplicated
        expr: sum(kafka_server_replicamanager_underreplicatedpartitions) > 0
        for: 5m
        labels:
          severity: P1
        annotations:
          summary: "Kafka 副本不足分区持续 5 分钟,检查 Broker 与磁盘"

      - alert: KafkaBrokerDown
        expr: absent(kafka_server_replicamanager_partitioncount{instance="10.0.0.16:9308"})
        for: 2m
        labels:
          severity: P0
        annotations:
          summary: "Kafka Broker 10.0.0.16 无指标,疑似掉线"

指标名以 kafka-exporter 版本为准,上线前先用 /metrics 页面核对一遍。

6.4 重分配期间的专项看板

重分配时普通告警会"合理地"变多,值班同学容易误判。建议在 Grafana 上临时开一组面板:

面板指标正常形态
迁移流量rate(kafka_server_brokertopicmetrics_bytesin_total[5m])稳定在 throttle 附近
URP 趋势上述 URP 指标先升后降,最终归 0
各 Broker 分区数kafka_server_replicamanager_partitioncount逐步收敛到均值
Leader 分布kafka_server_replicamanager_leadercount执行后趋于均匀
在线延迟业务侧 P99无显著变化

判断重分配是否健康,只看一条:URP 的斜率。 斜率为负,在追;斜率为零且大于 0,卡住了;斜率为正,在恶化,准备调大 throttle 或排查磁盘。


七、一次 3→6 扩容的完整时间线

把上面所有动作串起来,这是我们复盘后写进 Runbook 的标准流程:

时间动作验证点失败回退
T-1 天容量/带宽/分区数三笔账;确认版本与顺序性磁盘余量 >30%,迁移窗口足够调整方案或延期
T-0新 Broker 配置、格式化、启动三信号:进程、成员列表、Controller 注册检查 node.id / cluster.id / quorum
T+5min生成重分配方案当前 vs 建议两段 JSON 已保存重新生成
T+10min审阅:副本数、机架、单机分区数、Leader 倾斜单机副本数偏离均值 <50%手改 JSON 或换方案
T+15min--execute --throttle 100MB/s迁移流量稳定、在线 P99 无变化立即停命令,已迁移部分不回滚
T+20min盯 URP 斜率斜率为负调大 throttle 或排查磁盘
T+45min--verify 全部 completed新 Broker 出现在 Replicas;URP=0用同一 JSON 重跑
T+50min检查分区/Leader 均衡各 Broker 偏差 <20%微调方案再搬一轮
T+55min更新监控 target、Runbook、容量台账新 Broker 指标可见补抓取配置

整场操作里最危险的两步是 T+15 的 --execute 和 缩容时的停机:前者可能打满带宽,后者可能触发 Leader 风暴。这两步必须有人在看板前盯着,不能脚本一跑就去干别的。


八、常见问题

8.1 新 Broker 加进来了,为什么一个分区都没有?

这是 Kafka 的正常行为:已有分区不会因为新节点加入自动迁移,否则每次扩容都要全量搬数据。必须手动执行 kafka-reassign-partitions.sh,把分区"请"过去。

8.2 --generate 的方案能直接执行吗?

不建议。它只做"尽量均衡",不保证副本跨机架、不保证单 Broker 分区数合理,也可能产生大量不必要的迁移。执行前至少核对 3.3 的四项,尤其是副本数和机架分布。

8.3 重分配期间 URP 上升,要不要立刻停?

不用。新副本正在追 Leader,URP 短时上升是预期行为。看趋势:下降就继续,15 分钟不降再排查。真正要立刻停的信号是在线业务 P99 显著恶化,或者 Broker 磁盘 IO util 持续 100%。

8.4 --throttle 的单位是什么?设多少合适?

字节/秒,按 Broker 计。100MB/s 写 104857600。同机房万兆可给 200–500MB/s;跨机房给 50–100MB/s;业务高峰给 20–50MB/s。核心原则是给在线流量留出至少 50% 的带宽余量。

8.5 重分配跑到一半断了,数据会不会坏?

不会。重分配是幂等的:已完成的副本切换不回滚,未完成的继续复制。重新执行同一份 JSON 即可,别重新 --generate。

8.6 扩分区为什么会导致顺序问题?

分区数变了,hash(key) % 分区数 的结果就变了。同一个 key 的历史消息留在老分区,新消息可能进新分区,两个分区由不同消费者并行处理,顺序就乱了。顺序敏感 Topic 要么停写扩容,要么一步规划到位。

8.7 缩容能直接停进程吗?

不能。直接 kill -9 会让它领导的分区在 replica.lag.time.max.ms(默认 30 秒)内无 Leader。正确做法是先把副本迁走,再用 kafka-server-stop.sh / SIGTERM 走 controlled shutdown,让它先交接领导权。

8.8 重分配能不能顺便改副本数?

可以,改 JSON 里每条 replicas 的长度即可。但有两条红线:不能低于 min.insync.replicas(否则直接拒写),不能把副本全放在同一机架(容灾失效)。改完记得同步更新容量台账。

8.9 KRaft 模式下还能看 ActiveControllerCount 吗?

这个 JMX 指标是 ZK 时代的。KRaft 用 kafka-metadata-quorum.sh describe --status 看 LeaderId、Voters、Follower 落后情况;监控上可以采集该命令输出,或用支持 KRaft 的 exporter 版本。

8.10 集群多大需要上 Cruise Control?

经验线是 10 台 Broker 以上、Topic 数超过 200、或者每周都要做重分配。低于这个规模,手工流程 + 本文的检查表足够;超过之后,人工审阅 JSON 的边际成本会快速超过引入 Cruise Control 的成本。


九、总结

决策树

集群要动,先问一句:缺什么?
├── 吞吐/容量不够
│   ├── 分区数还有余量、顺序不敏感 → 扩分区
│   └── 分区数到顶或顺序敏感 → 加 Broker + 重分配
├── 容灾不够(副本同机架)
│   └── 加新机架 Broker + 重分配时审机架分布
├── 单机热点(分区/Leader 倾斜)
│   └── 只做重分配,不加机器
└── 要下线机器
    ├── 先重分配把副本搬空
    ├── verify 确认 Replicas 无目标 Broker
    └── controlled shutdown,绝不 kill -9

全流程速查表

步骤命令/动作必查项
加 Broker配置 → kafka-storage.sh format -t → 启动node.id 唯一、cluster.id 一致、quorum 正确
生成--generate --broker-list保存 current 与 proposed 两段
审阅统计副本数/机架/Leader副本数=RF、跨机架、单机偏差<50%
执行--execute --throttle带宽留 50% 余量
验证--verify + URP + describecompleted、URP 归零、分布均衡
缩容重分配排除 → verify → 优雅停机Replicas 无目标 Broker、controlled shutdown
监控URP/Offline/ISR/磁盘/LagP0 即时、P1 工作时段、5 分钟去重

一句话

Kafka 的高可用运维是一条有顺序的流水线:扩容先加节点、再搬分区、最后验证,少一步新机器就空转;缩容先搬空、再停机,反一步就是 Leader 风暴;重分配必须限流,扩分区必须考虑顺序,监控必须盯 URP 的斜率——命令谁都会敲,值钱的是"先做什么、做完看什么、出事退哪里"。

关键数据

  • 新 Broker 注册成功 ≠ 承担分区,重分配才是扩容的最后一公里
  • --throttle 单位是字节/秒/ Broker,跨机房建议 50–100MB/s
  • URP 短时上升是重分配的正常现象,看斜率不看绝对值
  • replica.lag.time.max.ms 默认 30 秒,直接 kill Broker 会造成最长 30 秒的分区不可用
  • 扩分区只增不减,且不迁移存量数据,hash 漂移会破坏同 key 顺序
  • 告警收敛:P0 即时、P1 工作时段、P2 延迟到早晨,同一告警 5 分钟内只发一次

给团队的建议

项目状态建议
计划扩容先按本文三笔账算一遍,再确认顺序性约束
扩容后新机器空转不是故障,是漏了重分配,按第三章补
重分配经常影响业务把 --throttle 写进 Runbook 模板,默认值 100MB/s
缩容靠 kill立刻改成"迁移 + controlled shutdown"流程
监控只有 Lag先把 URP、OfflinePartitions、磁盘三条加上
集群超过 10 台评估 Cruise Control,别再手工审 JSON
值班总被同一告警吵检查 Alertmanager 的 repeat_interval 与时区配置

互动话题:你们扩容 Kafka 时,新 Broker 空转过多久?重分配有没有因为没限流把在线业务拖慢过?评论区聊聊你的翻车现场,点赞最高的案例我拆成下一篇完整复盘。


参考资料


标题:Kafka 扩容后新 Broker 空转 3 小时:加机器不是扩容,重分配才是
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/11/01/1793462611279.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消