Kafka(MSK)topic 增加副本数操作

适用场景与原则

适用场景:topic 的复制因子(ReplicationFactor)小于 3,需要在不停服的情况下增加到 3。常见于程序自动创建的 topic,或早期手工创建时只配了 2 个副本。

推荐配置:生产环境的 topic 统一使用以下组合。

配置

推荐值

作用

复制因子

3

每个分区 3 个副本,分布在 3 个可用区的 broker 上

min.insync.replicas

2

写入至少 2 个副本才算成功

生产者 acks

all

让 min.insync.replicas 生效

unclean.leader.election.enable

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 步的命令中加上 --additional 并修改 --throttle 的值(例如 20000000)重新执行,只更新限速,不会重复提交

需要中止正在进行的重新分配

./kafka-reassign-partitions.sh --bootstrap-server $BS $CFG --cancel --reassignment-json-file /tmp/rf3.json,已经完成的分区不受影响,未完成的会恢复原来的副本

完成后需要恢复成原来的副本数

把第 3 步输出中保存的 Current partition replica assignment 写入 /tmp/rollback.json,用它重新执行第 3~4 步

--verify 一直显示 in progress

检查 broker 磁盘、网络和限速是否过低;查看 --describe --under-replicated-partitions;数据量大时属于正常现象,继续等待

执行时报 There is an existing assignment running

上一次重新分配还没完成,先 --verify 确认它完成后再提交

忘记执行 --verify,限速残留

用当时的 JSON 文件补执行一次 --verify,会清除限速;JSON 文件丢失时,检查各 broker 的 leader.replication.throttled.rate 和 follower.replication.throttled.rate 动态配置并手动删除

手动清除残留限速:

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

注意事项

  1. 主副本放第一位:Kafka 把副本列表的第一个作为“首选主副本”。保持原主副本在第一位,操作中不会发生主副本切换;如果顺序写错,后续自动平衡时主副本会迁移,引起客户端短暂重连。

  2. 只增加副本,不改分区数:本手册只调整副本数。增加分区是另一个操作(kafka-topics.sh --alter --partitions),会影响按 key 分区的消息顺序,需要单独评估。

  3. 磁盘占用增加约 50%:2 副本改 3 副本后,该 topic 占用的总磁盘增加一半。批量处理前先评估总量。

  4. 限速按数据量调整:50MB/s 适合大多数场景;broker 负载高时降低到 10~20MB/s,空闲时段可提高。

  5. 一次只跑一个重新分配:上一个没完成前提交新的会报错,即使用 --additional 叠加也会增加集群压力。

  6. 避开 MSK 维护窗口:重新分配期间不要修改集群配置、升级版本或扩缩容,这些操作会滚动重启 broker。

  7. 命令行工具版本:工具比集群新时会有 WARN 提示但能正常使用;工具比集群旧太多可能不支持 --bootstrap-server 等参数,建议使用 2.6 以上版本。

  8. 保留回滚方案:每次 --execute 的输出都保存到操作记录中。

预防:集群默认配置与客户端要求

副本数不足的 topic 大多是程序自动创建的,从源头修正集群默认值,避免反复处理。

集群配置(MSK 控制台 → 集群 → 属性 → 配置,修改会触发 broker 滚动重启,在低峰期操作):

参数

推荐值

default.replication.factor

3

min.insync.replicas

2

unclean.leader.election.enable

false

auto.create.topics.enable

生产环境建议 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。

posted @ 2026-10-18 02:38  MR__Wang  阅读(3)  评论(0)    收藏  举报