Kafka(MSK)topic 增加副本数操作
适用场景与原则
适用场景:topic 的复制因子(ReplicationFactor)小于 3,需要在不停服的情况下增加到 3。常见于程序自动创建的 topic,或早期手工创建时只配了 2 个副本。
推荐配置:生产环境的 topic 统一使用以下组合。
|
配置 |
推荐值 |
作用 |
|---|---|---|
|
复制因子 |
3 |
每个分区 3 个副本,分布在 3 个可用区的 broker 上 |
|
|
2 |
写入至少 2 个副本才算成功 |
|
生产者 |
all |
让 |
|
|
false |
不允许落后的副本成为主副本,防止丢数据 |
这个组合下,任意 1 个 broker 故障或重启(包括 MSK 的例行维护和滚动升级)都不影响读写;2 个同时故障时拒绝写入,宁可暂停也不丢数据。
为什么 2 副本必须改:2 副本加 min.insync.replicas=2 时,任何一个 broker 重启,它上面的分区就只剩 1 个同步副本,无法写入。MSK 每次打补丁、改配置都会滚动重启 broker,业务会周期性写入失败。
操作原则:
-
通过分区重新分配(
kafka-reassign-partitions.sh)在线增加副本,分区数不变,读写不中断 -
当前主副本放在新副本列表的第一位,过程中不切换主副本,客户端无感知
-
复制数据时限速,避免占满 broker 带宽
-
完成后必须执行
--verify,确认完成并清除限速
前置检查
在能访问 MSK 的机器上(同一 VPC),使用与集群版本相近的 Kafka 命令行工具。以下命令都在 kafka/bin 目录下执行。
1. 设置变量
cd /usr/local/kafka/bin
BS=<bootstrap 地址> # MSK 控制台 → 集群 → 查看客户端信息,多个地址用逗号分隔
CFG="" # 9092 明文端口留空;IAM/TLS 认证时改为 CFG="--command-config client.properties"
2. 确认 broker 数量和 ID
./kafka-broker-api-versions.sh --bootstrap-server $BS $CFG 2>/dev/null | grep -oE 'id: [0-9]+' | sort -u
目标副本数不能超过 broker 数量,3 副本至少需要 3 个 broker,并且最好分布在 3 个可用区。
3. 集群当前健康
./kafka-topics.sh --bootstrap-server $BS $CFG --describe --under-replicated-partitions 2>/dev/null
没有输出才继续。已经有副本不同步时,先排查原因,不要叠加重新分配。
4. 评估数据量和磁盘
新增的副本要从主副本完整复制一份数据,每个 broker 的磁盘占用会相应增加。在 MSK 控制台查看 topic 的大小和各 broker 的磁盘使用率(CloudWatch 指标 KafkaDataLogsDiskUsed),确认增加后不超过 80%。
5. 选择时间
数据量小的 topic 几秒即可完成,可随时操作;数据量大的 topic(几十 GB 以上)放在业务低峰期。
命令执行时出现 The DescribeTopicPartitions API is not supported, using Metadata API 的 WARN 可以忽略,这是命令行工具版本比集群新导致的,结果不受影响。
单个 topic 操作步骤
第 1 步:查看当前分布
TOPIC=<topic 名称>
./kafka-topics.sh --bootstrap-server $BS $CFG --describe --topic $TOPIC
记下每个分区的 Leader(主副本)和 Replicas(副本列表)。
第 2 步:编写重新分配方案
规则:每个分区保留原有副本,当前主副本放在第一位,再补上缺少的 broker,补齐到 3 个且不重复。示例:
|
分区 |
当前(主副本) |
新方案 |
|---|---|---|
|
0 |
3,1(主 3) |
3,1,2 |
|
1 |
2,3(主 2) |
2,3,1 |
|
2 |
1,2(主 1) |
1,2,3 |
cat > /tmp/rf3.json << 'EOF'
{"version":1,"partitions":[
{"topic":"<topic 名称>","partition":0,"replicas":[3,1,2]},
{"topic":"<topic 名称>","partition":1,"replicas":[2,3,1]},
{"topic":"<topic 名称>","partition":2,"replicas":[1,2,3]}
]}
EOF
python3 -m json.tool /tmp/rf3.json > /dev/null && echo "JSON 格式正确"
分区较多时,用下一节的脚本自动生成,避免手工填错。
第 3 步:执行(限速)
./kafka-reassign-partitions.sh --bootstrap-server $BS $CFG \
--reassignment-json-file /tmp/rf3.json --execute --throttle 50000000
-
--throttle 50000000:副本复制限速约 50MB/s -
输出中的
Current partition replica assignment是回滚方案,复制保存下来 -
看到
Successfully started partition reassignments表示已开始
第 4 步:验证并清除限速
./kafka-reassign-partitions.sh --bootstrap-server $BS $CFG \
--reassignment-json-file /tmp/rf3.json --verify
重复执行,直到所有分区都显示 is completed,并出现 Clearing broker-level throttles 和 Clearing topic-level throttles。这一步不能省略,否则限速会一直留在 broker 上。
第 5 步:确认结果
./kafka-topics.sh --bootstrap-server $BS $CFG --describe --topic $TOPIC
-
ReplicationFactor: 3 -
每个分区的
Replicas有 3 个 broker -
每个分区的
Isr有 3 个 broker(顺序可以不同) -
每个分区的
Leader与操作前一致 -
MSK 控制台中该 topic 的“不同步副本数”为 0
批量处理多个 topic
1. 找出所有副本数小于 3 的 topic
./kafka-topics.sh --bootstrap-server $BS $CFG --describe 2>/dev/null \
| grep -E '^Topic:.*ReplicationFactor: ?[12][[:space:]]' | awk '{print $2}' | tee /tmp/low_rf_topics.txt
wc -l < /tmp/low_rf_topics.txt
2. 自动生成重新分配方案
脚本规则:只处理副本数小于目标值的分区;当前主副本放第一位,保留原有副本;补副本时优先选择当前副本数最少的 broker,保持负载均衡。已经是 3 副本的分区不会出现在方案里。
cat > /tmp/gen_rf.sh << 'EOF'
#!/bin/bash
# 用法:BROKERS="1 2 3" TARGET_RF=3 ./gen_rf.sh < describe 输出
TARGET_RF=${TARGET_RF:-3}
awk -v brokers="${BROKERS}" -v rf="${TARGET_RF}" '
/Partition: [0-9]/ {
for (i = 1; i <= NF; i++) {
if ($i == "Topic:") t = $(i+1)
if ($i == "Partition:") p = $(i+1)
if ($i == "Leader:") l = $(i+1)
if ($i == "Replicas:") r = $(i+1)
}
n++; T[n] = t; P[n] = p; L[n] = l; R[n] = r
nr = split(r, rr, ","); for (j = 1; j <= nr; j++) load[rr[j]]++
}
END {
nb = split(brokers, b, " ")
for (k = 1; k <= n; k++) {
nr = split(R[k], rr, ",")
if (nr >= rf) continue
delete seen; list = L[k]; seen[L[k]] = 1; cnt = 1
for (j = 1; j <= nr; j++) if (!(rr[j] in seen)) { list = list "," rr[j]; seen[rr[j]] = 1; cnt++ }
while (cnt < rf) {
best = ""
for (j = 1; j <= nb; j++) if (!(b[j] in seen) && (best == "" || load[b[j]] < load[best])) best = b[j]
if (best == "") { printf "broker 数量不足,无法达到 %d 副本\n", rf > "/dev/stderr"; exit 1 }
list = list "," best; seen[best] = 1; load[best]++; cnt++
}
out = out sep sprintf("{\"topic\":\"%s\",\"partition\":%s,\"replicas\":[%s]}", T[k], P[k], list); sep = ",\n "
}
printf "{\"version\":1,\"partitions\":[\n %s\n]}\n", out
}'
EOF
chmod +x /tmp/gen_rf.sh
3. 每次处理一批 topic
建议每批 5~10 个 topic,数据量大的单独处理。下面以前 10 个为例:
head -10 /tmp/low_rf_topics.txt | while read -r t; do
./kafka-topics.sh --bootstrap-server $BS $CFG --describe --topic "$t" 2>/dev/null
done | BROKERS="1 2 3" /tmp/gen_rf.sh > /tmp/rf3_batch1.json
python3 -m json.tool /tmp/rf3_batch1.json # 人工检查方案
检查要点:每个分区 3 个不重复的 broker,第一个与当前主副本一致。确认后按“单个 topic 操作步骤”的第 3~5 步执行,把文件名换成 /tmp/rf3_batch1.json,完成一批再处理下一批。
同一时间只能有一个重新分配在进行。上一批没有 --verify 到全部完成前,不要提交下一批。
回滚与异常处理
|
情况 |
处理 |
|---|---|
|
复制太慢,影响业务 |
调低限速:在第 3 步的命令中加上 |
|
需要中止正在进行的重新分配 |
|
|
完成后需要恢复成原来的副本数 |
把第 3 步输出中保存的 |
|
|
检查 broker 磁盘、网络和限速是否过低;查看 |
|
执行时报 |
上一次重新分配还没完成,先 |
|
忘记执行 |
用当时的 JSON 文件补执行一次 |
手动清除残留限速:
for b in 1 2 3; do
./kafka-configs.sh --bootstrap-server $BS $CFG --entity-type brokers --entity-name $b --describe
./kafka-configs.sh --bootstrap-server $BS $CFG --entity-type brokers --entity-name $b --alter \
--delete-config leader.replication.throttled.rate,follower.replication.throttled.rate
done
注意事项
-
主副本放第一位:Kafka 把副本列表的第一个作为“首选主副本”。保持原主副本在第一位,操作中不会发生主副本切换;如果顺序写错,后续自动平衡时主副本会迁移,引起客户端短暂重连。
-
只增加副本,不改分区数:本手册只调整副本数。增加分区是另一个操作(
kafka-topics.sh --alter --partitions),会影响按 key 分区的消息顺序,需要单独评估。 -
磁盘占用增加约 50%:2 副本改 3 副本后,该 topic 占用的总磁盘增加一半。批量处理前先评估总量。
-
限速按数据量调整:50MB/s 适合大多数场景;broker 负载高时降低到 10~20MB/s,空闲时段可提高。
-
一次只跑一个重新分配:上一个没完成前提交新的会报错,即使用
--additional叠加也会增加集群压力。 -
避开 MSK 维护窗口:重新分配期间不要修改集群配置、升级版本或扩缩容,这些操作会滚动重启 broker。
-
命令行工具版本:工具比集群新时会有 WARN 提示但能正常使用;工具比集群旧太多可能不支持
--bootstrap-server等参数,建议使用 2.6 以上版本。 -
保留回滚方案:每次
--execute的输出都保存到操作记录中。
预防:集群默认配置与客户端要求
副本数不足的 topic 大多是程序自动创建的,从源头修正集群默认值,避免反复处理。
集群配置(MSK 控制台 → 集群 → 属性 → 配置,修改会触发 broker 滚动重启,在低峰期操作):
|
参数 |
推荐值 |
|---|---|
|
|
3 |
|
|
2 |
|
|
false |
|
|
生产环境建议 false,topic 由运维统一创建 |
手工创建 topic 的标准命令:
./kafka-topics.sh --bootstrap-server $BS $CFG --create --topic <topic 名称> \
--partitions 3 --replication-factor 3 --config min.insync.replicas=2
客户端要求(交给研发确认):
-
生产者
acks=all,否则min.insync.replicas不生效 -
生产者开启重试(
retries默认值即可),broker 滚动重启时会自动重发 -
topic 名和消费组名不要混用,避免把消费组名误写成 topic 名导致自动创建出新 topic
定期巡检:每月执行一次“批量处理”第 1 步的查询,确认没有新的副本数不足的 topic。

浙公网安备 33010602011771号