现场变更 · 两大主题

Kafka 现场变更手册
分区扩容 + 10MB 大消息配置链

两条主线 1. 分区扩容本身是「平滑」的(不搬数据、自动均衡、只付一次 consumer rebalance);真正不平滑的是 key → partition 映射被破坏

2. 10MB 大消息需要 producer / broker / consumer 全链一起放大;客户目前只做完了 broker 侧的一半,另外四项未知,其中三项默认值是 1 MiB。
Part 01增加分区

1.1 命令

kafka-topics.sh --bootstrap-server $BS --alter --topic my_topic --partitions 36
⚠️ --partitions 给的是目标总数,不是增量。
36 = 「扩到 36 个」,不是「再加 36 个」。

MSK 上同样是这条命令——这是 topic 层面的操作,不需要走 MSK 配置修订版(那是 broker 配置的路径)。

1.2 新分区会自动分布到整个集群吗?

会,但要说准。

新分区的 leader / replica 位置由 Kafka 自动分配,走 AdminUtils 的副本分配算法:

  1. 每个分区的第一个副本(= preferred leader)在 broker 列表上做 round-robin,起始位置随机
  2. 剩余副本按递增 shift 排布
  3. 有 rack 信息时(MSK 自动把 broker.rack 设为 AZ)→ 遵守跨 AZ 分散约束

所以新增的 20 个分区(16 → 36)会被均匀撒到全部 12 个 broker,无需手工指定。

两个必须清楚的关键点

 说明
不是「只落在空闲 / 新 broker 上」。若场景是「刚加了 3 台新 broker,想让新分区只去新机器」,默认算法不会这么做——它会撒到全部 15 台。要强制只落新机器,必须用 --replica-assignment 手工指定,--alter 时要把全部分区的 assignment 写全,不只是新增的。
36 = 12 × 3 这个数选得好:正好每台 broker 3 个 leader,完全均衡。当前 16 分区在 12 台上是「4 台各扛 2 个 + 8 台各扛 1 个」——那 4 台的 leader 负载是别人的 2 倍,而集群平均 CPU 会掩盖这个倾斜。

1.3 是不是「平滑」的?分三层看

第 1 层✅ 不搬任何数据(最重要的好消息)

加分区完全不触发数据迁移。 新分区在目标 broker 上是全新的空分区,直接创建,零字节复制

这与 kafka-reassign-partitions.sh(副本重分配)是完全不同的两回事——后者才是客户担心的「搬数据、吃 CPU / 网络、AWS 说 CPU > 70% 不要做」。

客户「怕 rebalance 所以不敢动」的顾虑,对「加分区」这个动作是不成立的
第 2 层⚠️ 会触发一次 consumer group rebalance(唯一的运行时代价)
  • 消费端最长 5 分钟metadata.max.age.ms 默认 300000)后才感知到新分区
  • 感知后触发重分配。默认 RangeAssignoreager 协议 = stop-the-world:全组先 revoke 全部分区 → 全组停止消费 → 重新分配
  • 10× 峰值下,停 30 秒 = 凭空制造「30 秒 × 10 倍生产速率」的 lag

缓解:切 CooperativeStickyAssignor(增量式,只影响需要迁移的那几个分区,不是全组停摆)。
⚠️ 注意:必须两轮滚动升级,不能一步切

另一个副作用:这 5 分钟窗口内,新分区已在收数据但还没人消费 → 会看到「新分区 lag 快速上涨」的假故障。想立刻生效就滚动重启 consumer。

第 3 层🔴 破坏 key → partition 映射(真正不平滑的地方)

这是对交易类客户最要紧的一条,也是唯一可能造成正确性事故的。

默认 partitioner:

toPositive(murmur2(serializedKey)) % numPartitions

分区数从 16 变 36,同一个 key 的落点几乎必然改变。举例:某账户 key 的 murmur2 正值为 1000

分区数1000 % N该 key 的消息去哪
168partition-8
3628partition-28

后果链

  1. 扩容那一刻,该 key 的新消息开始写 partition-28
  2. 但它的旧消息还在 partition-8,且可能没消费完
  3. 两个分区之间没有任何顺序保证(由不同消费者并行消费)
  4. 下游可能先看到「撤单」再看到「下单」、同账户入金/出金乱序导致中间态负余额、订单状态机倒退
对交易 / 撮合 / 账务,这是正确性问题,不是性能问题。

顺带一个更严重的情况

单分区 topic 扩容时,key 映射破坏率是 100%——因为 hash % 1 == 0 对所有 key 成立,扩容后除了恰好留在 partition-0 的那部分,其余 key 全部换分区。比 16 → 36 严重得多。

1.4 所以现场必须先问这一句

「这些 topic 的 producer 有指定 message key 吗?key 是什么?下游依赖同一个 key 的消息严格有序吗?」
答案能否随手 --alter
无 key(走 sticky partitioner)✅ 可以 随时扩,零风险,唯一代价是一次 rebalance
有 key 但下游不依赖有序✅ 可以扩
有 key 且依赖有序🔴 不能随手做 必须走下面三个方案之一
不知道🔴 保守处理 按「有且依赖」处理(交易业务不能赌)

有 key 时的三个方案(按运维成本排序)

方案做法顺序风险适用
1. 排空后再扩停写/限速 → 等所有 group 的 LAG 严格归零(不是「接近 0」)→ --alter → 滚动重启 consumer → 恢复写入0明天最推荐:反正已有变更窗口(机型升级),顺手做
2. 新 topic 双写切换topic-v2(目标分区数)→ producer 双写 → 等 v1 排空 → consumer 切 v2 → 下线 v10无法停写的核心交易 topic
3. 自定义 Partitionerhash(key) % 12288 固定虚拟槽,再映射到物理分区区间;扩容只改映射表大幅降低
(非完全消除)
预期还会持续扩容的集群

方案 1 的验收标准为什么必须是严格 0:只要还有一条带某 key 的旧消息未消费,扩容后该 key 的新消息就可能被消费掉,顺序就断了。

1.5 还有两个代价要一起知道

① 只增不减,硬编码拒绝

controller 直接抛 InvalidPartitionsException: would not be an increase。要减只能重建 topic(没有 in-place 路径)。

推论:分区数是单向棘轮。宁可一次算准甚至适度 over-partition,别「先加一点看看不够再加」——每次加都要付一次顺序性代价

好在客户空间极大:

36 分区 × RF3 ÷ 12 broker = 每 broker 仅 9 个副本
AWS 对 4xlarge+ 的推荐上限 = 4000

按 12 ~ 24 个月后的目标一次定准完全可行。

② 老数据不重分布

官方原文:

adding partitions doesn't change the partitioning of existing data... Kafka will not attempt to automatically redistribute data in any way

这条直接决定了时间安排

场景加分区有用吗
积压已经发生,要清 lag❌ 几乎无用 旧积压全在老分区里,还是那批消费者慢慢啃;新空分区分到的实例在空转,短期内有效并行度反而更低
积压尚未发生,为峰值做准备✅ 唯一解
时间安排结论 加分区是预防措施,不是急救措施。必须在 10× 峰值到来之前做完。
明天既然已有变更窗口,就该把分区扩容和机型升级一起做掉,而不是留到出问题时再动。
Part 0210MB 大消息的全链配置

2.1 客户当前值

brokermessage.max.bytes = 10485880    # 约 10.0 MB

一个值得注意的细节:这不是整数的 10 MiB

10 × 1024 × 1024 = 10485760
客户值             10485880
差值                    120 字节

这 120 字节很可能是客户有意为 record batch header 预留的——说明他们是认真算过的,确实在传 10MB 量级的消息。

这是一个可以夸客户的细节,也可以当作「我们逐字节核对过你的配置」的证据。

但这 120 字节踩了一个很阴的坑

replica.fetch.response.max.bytes 的官网默认值恰好是 10485760(整 10 MiB)。

也就是说,客户的 message.max.bytes 比这个默认值高出正好 120 字节

每条满载的 10MB 消息
  → 触发 fetch response 上限的 fallback 路径(「整个 response 只装这一条」)
  → 在 12 broker 的复制拓扑上显著拖慢复制吞吐
  → 喂给 ISR 收缩风险

2.2 「决定发送数据包上限」——这里要分清两侧

message.max.bytesbroker 侧的接收闸门(broker 愿意接受的单个 batch 上限),它不是 producer 侧的发送上限

producer 侧管发送上限的是 max.request.size(默认仅 1 MiB)

两者关系:

producer  max.request.size          ≥ 单条消息大小
                                     ← 不满足:produce 客户端直接抛 RecordTooLargeException
broker    message.max.bytes         = 10485880    ✅ 客户已设对
broker    socket.request.max.bytes  = 100MB       ✅ 有 10× 余量
所以 客户把 broker 侧的 message.max.bytes 设到 10MB 是对的、已经做完的一半
危险的是另一半——producer 的 max.request.size 如果还是默认 1 MiB,10MB 的消息根本发不出去

这正是明天要现场问出来的 P0 第一个问题

2.3 完整约束链(10MB 消息必须全链一起放大)

参数官网默认客户当前应设为
max.request.sizeproducer1 MiB未知 🔴≥ 12 MiB
message.max.bytesbroker104858810485880 保持
replica.fetch.max.bytesbroker1 MiB未知 🔴≥ 12 MiB(read-only 需重启
replica.fetch.response.max.bytesbroker10485760未知 🔴≥ 32 MiB(客户值超它 120B)
max.partition.fetch.bytesconsumer1 MiB未知 🔴≥ 12 MiB
fetch.max.bytesconsumer50 MiB未知≥ 上一项

四个「未知」的后果

四个未知里有三个默认值是 1 MiB。只要有一个没跟着放大:

漏掉的参数后果
max.request.sizeproduce 直接失败RecordTooLargeException
replica.fetch.max.bytes / replica.fetch.response.max.bytes复制退化 → ISR 收缩 → 生产被拒
max.partition.fetch.bytes消费退化,约 12× 效率损失 ← 这很可能就是「消费跟不上」的直接原因
Part 03现场提问清单与行动清单

3.1 现场必须问出来的(P0)

#问题为什么是 P0
1producer 的 max.request.size 是多少?若仍是默认 1 MiB,10MB 消息根本发不出去
2replica.fetch.max.bytes / replica.fetch.response.max.bytes 是多少?复制退化 → ISR 收缩 → 生产被拒
3consumer 的 max.partition.fetch.bytes 是多少?很可能是「消费跟不上」的直接原因(~12× 损失)
4producer 有指定 message key 吗?key 是什么?下游依赖同一 key 严格有序吗?决定分区扩容能否随手 --alter

3.2 变更窗口内的行动清单

大消息链(优先,因为可能是当前问题的根因)
  • 核对四个未知参数的实际值
  • producer:max.request.size → ≥ 12 MiB
  • consumer:max.partition.fetch.bytes → ≥ 12 MiB;fetch.max.bytes ≥ 前者
  • broker:replica.fetch.max.bytes → ≥ 12 MiB(read-only,必须重启,正好搭机型升级的重启)
  • broker:replica.fetch.response.max.bytes → ≥ 32 MiB(消掉那 120 字节的坑)
分区扩容
  • 确认每个待扩 topic 的 key / 有序性要求
  • 无 key 或不依赖有序 → 直接 --alter12 的整数倍(建议 36)
  • 有 key 且依赖有序 → 走方案 1:排空(LAG 严格 = 0)→ --alter → 滚动重启 consumer
  • 顺手评估 CooperativeStickyAssignor 迁移(两轮滚动)
  • 分区数按 12 ~ 24 个月目标一次定准,别分次加

3.3 一句话总结

加分区本身平滑——不搬数据、自动均衡到全集群、代价只有一次 consumer rebalance;不平滑的是 key 顺序性,有 key 且依赖有序时必须「排空后再扩」。
10MB 大消息客户只做完了 broker 侧的一半,producer / replica-fetch / consumer 三处若仍是 1 MiB 默认值,分别对应 produce 失败、ISR 收缩、消费退化——这三项才是明天要先钉死的

本文档技术内容未做增删改,仅完成两大主题合并、表格格式统一、P0 问题与行动清单合并。

不含任何客户名 / 公司名 / 账号 / IP / 主机名等敏感信息。配套完整调优手册见 MSK-Tuning-Battle-Plan.html