我不会起名字322 · 后端 / 算法 / 数据库
文章目录
- [Kafka 分区数到底设几个:吞吐量、顺序性与 rebalance 代价的取舍](#Kafka 分区数到底设几个:吞吐量、顺序性与 rebalance 代价的取舍)
-
- 一、为什么这个问题没有标准答案
- 二、先算吞吐:分区数决定并行度上限
- [三、再算顺序性:分区数会破坏 key 的顺序](#三、再算顺序性:分区数会破坏 key 的顺序)
- [四、然后算 rebalance 代价](#四、然后算 rebalance 代价)
- 五、一个实用的决策流程
- 六、分区数只能加不能减
- 七、还有几个容易忽略的点
-
- 分区数最好是消费者数的整数倍
- 副本数也要一起考虑
- [消息 key 的分布要均匀](#消息 key 的分布要均匀)
- 用自定义分区器解决热点
- 八、带走物
Kafka 分区数到底设几个:吞吐量、顺序性与 rebalance 代价的取舍
一、为什么这个问题没有标准答案
分区数同时影响四件事,而且方向不一致:
| 维度 | 分区多了 | 分区少了 |
|---|---|---|
| 吞吐 | ↑ 更多并行 | ↓ 并行度受限 |
| 顺序性 | ↓ 同一 key 分散 | ↑ 更容易保证 |
| rebalance 代价 | ↑ 分区多,重分配慢 | ↓ |
| 端到端延迟 | ↑ 分区多,更多 batch 等待 | ↓ |
没有"最优解",只有"在你的约束下的平衡点"。 而且这四个维度的权重,取决于你的业务是什么类型。
二、先算吞吐:分区数决定并行度上限
一条硬规则 :一个分区在同一时刻只能被一个消费者线程消费。
所以:
text
消费并行度上限 = min(分区数, 消费者实例数 × 每实例线程数)
分区数 < 消费者数时,多出来的消费者完全空闲。
怎么估需要的分区数
先算目标吞吐:
text
目标吞吐 = 峰值消息量(条/秒)× 单条处理耗时(秒)= 需要的并行度
举例:
text
峰值 5000 条/秒,单条处理 20ms(0.02s)
→ 需要 5000 × 0.02 = 100 个并行处理能力
如果每个消费者线程能跑满,那就需要 100 个分区。
但现实中要考虑两个折扣:
- 单线程不可能 100% 跑满(有 IO 等待),实际并行度打个 1.5~2 倍余量;
- 消费峰值 ≠ 生产峰值,消费可以滞后一点,不必完全实时。
所以实用公式:
text
分区数 ≈ 峰值TPS × 单条耗时 × 1.5
生产端也一样
生产者可以往多个分区并行写,分区数太少会限制生产吞吐。经验值:单个分区的生产吞吐大约 1~10 MB/s(取决于消息大小和压缩)。
三、再算顺序性:分区数会破坏 key 的顺序
关键机制 :默认分区器是 hash(key) % 分区数。
这意味着分区数一变,同一个 key 的落点就变了:
text
分区数 3:order_100 → hash % 3 = 1
分区数 4:order_100 → hash % 4 = 0
扩分区的瞬间,顺序性会被短暂破坏。 正在途中的消息在旧分区,新消息在新分区,两条不同的消费者可能以不同速度消费,导致后发的先被处理。
如果你的业务严格依赖 key 的顺序(比如订单状态机):
- 分区数不要频繁变;
- 非扩不可时,选低峰期 + 消费无积压的时候做;
- 或者一开始就按未来的峰值预留分区数(下面会讲代价)。
四、然后算 rebalance 代价
分区越多,rebalance 越贵:
- 分配计算更慢:分区多,coordinator 计算分配方案耗时更长;
- 停止消费的时间更长:rebalance 期间整个组停止消费,分区越多,恢复后要重新拉取的 offset、要重建的 fetch 请求越多;
- 元数据更大:消费者要维护更多分区的状态。
经验值:
text
分区数 < 50: rebalance 影响很小
分区数 50~200: 能感知到,但可接受
分区数 > 500: rebalance 代价明显,要谨慎
另外还有一个隐藏成本:每个分区在 broker 上都是一个目录 + 若干文件句柄。一个 broker 上几千个分区会导致:
- 启动时要加载大量日志段,恢复很慢;
- 文件句柄占用高;
- 选举 leader 变慢。
Kafka 官方的建议是单个 broker 上的分区数不超过 4000,整个集群不超过 200000。
五、一个实用的决策流程
text
1. 先算吞吐需求:分区数 ≥ 峰值TPS × 单条耗时 × 1.5
2. 检查顺序性约束:
├─ 依赖 key 顺序 → 分区数定下来就别轻易改,预留一点余量
└─ 不依赖顺序 → 可以更自由地扩
3. 检查 rebalance 敏感度:
├─ 消费者经常重启/发布 → 分区数别太大(< 200)
└─ 消费者稳定 → 可以放宽
4. 检查集群规模:
单 broker 分区数 < 4000
常见规模参考
| 场景 | 建议分区数 |
|---|---|
| 小流量(<100 TPS) | 3~6 |
| 中等(几百 TPS) | 12~24 |
| 较大(几千 TPS) | 48~96 |
| 高吞吐日志类(不要求顺序) | 100~200+ |
大多数业务 12~48 就够了。 我见过很多 topic 设了 100+ 分区但实际只跑了几个消费者,纯属浪费。
六、分区数只能加不能减
这是一个必须知道的硬限制:Kafka 不支持减少分区数。
bash
# 只能加
kafka-topics.sh --alter --topic order --partitions 24
# 减会报错
kafka-topics.sh --alter --topic order --partitions 6
# Error: The number of partitions for a topic can only be increased
原因:减少分区意味着要决定"被删掉的分区里的消息去哪",而已经消费的 offset 无法回退。
所以定分区数时要保守一点,宁可略多也不要一开始就卡死(但要平衡 rebalance 代价)。扩分区是有代价的(顺序性、rebalance),但至少可行。
如果真的要减:只能新建一个 topic,把数据迁过去,再切流量。这是大工程。
七、还有几个容易忽略的点
分区数最好是消费者数的整数倍
text
分区 12,消费者 4 → 每个消费者 3 个分区,均匀
分区 12,消费者 5 → 4 个消费者拿 2 个,1 个消费者拿 4 个,不均
不均的后果:拿多的那个消费者成为瓶颈,而且扩容第 5 个消费者之前,前 4 个的负载一直不均。
副本数也要一起考虑
分区数 × 副本数 = 实际partition 总数。
text
分区 24 × 副本 3 = 72 个 partition 副本分布在集群里
副本多会增加存储成本和同步开销,但提升可靠性。副本数建议 3(能容忍 1 个 broker 挂掉)。
消息 key 的分布要均匀
hash(key) % 分区数 只有在 key 分布均匀 时才均匀。如果 90% 的消息都是同一个 key(比如某个大客户的 ID),那么对应分区会成为热点------加分区也解决不了。
这种情况要改 key 设计(比如加随机后缀打散),或者用自定义分区器。
用自定义分区器解决热点
go
// sarama 自定义分区器:给热点 key 打散
type shardPartitioner struct {
topic string
}
func (p *shardPartitioner) Partition(msg *sarama.ProducerMessage, n int32) (int32, error) {
key, _ := msg.Key.Encode()
// 对热点 key 加随机后缀打散到多个分区
if isHotKey(string(key)) {
return int32(rand.Intn(int(n))), nil
}
h := fnv.New32a()
h.Write(key)
return int32(h.Sum32() % uint32(n)), nil
}
config.Producer.Partitioner = func(topic string) sarama.Partitioner {
return &shardPartitioner{topic: topic}
}
注意:打散热点 key 会破坏该 key 的顺序性------这是又一次取舍。
八、带走物
决策清单:
text
1. 算下限:分区数 ≥ 峰值TPS × 单条耗时 × 1.5
2. 看顺序性:依赖 key 顺序 → 定完就别改,扩分区会短暂破坏顺序
3. 看 rebalance:消费者经常重启 → 分区数控制在 200 以内
4. 看集群:单 broker 分区数 < 4000
5. 尽量让分区数是消费者数的整数倍
6. 记住:分区只能加不能减,一开始就留点余量
7. 检查 key 分布:热点 key 加分区没用,要改 key 或自定义分区器
一句话结论:分区数不是一个性能参数,而是一个"并行度 × 顺序性 × rebalance 代价"的三维取舍;而且它只能加不能减,所以第一次定值时就要把未来的峰值算进去。