Kafka 分区数到底设几个:吞吐量、顺序性与 rebalance 代价的取舍

我不会起名字322 · 后端 / 算法 / 数据库

📘 技术栈 | 🧩 力扣 Hot100 | 🐹 Go 项目 | 🟥 Redis | 🐬 MySQL

文章目录

Kafka 分区数到底设几个:吞吐量、顺序性与 rebalance 代价的取舍

一、为什么这个问题没有标准答案

分区数同时影响四件事,而且方向不一致:

维度 分区多了 分区少了
吞吐 ↑ 更多并行 ↓ 并行度受限
顺序性 ↓ 同一 key 分散 ↑ 更容易保证
rebalance 代价 ↑ 分区多,重分配慢 ↓
端到端延迟 ↑ 分区多,更多 batch 等待 ↓

没有"最优解",只有"在你的约束下的平衡点"。 而且这四个维度的权重,取决于你的业务是什么类型。

二、先算吞吐:分区数决定并行度上限

一条硬规则 :一个分区在同一时刻只能被一个消费者线程消费。

所以:

text 复制代码
消费并行度上限 = min(分区数, 消费者实例数 × 每实例线程数)

分区数 < 消费者数时,多出来的消费者完全空闲。

怎么估需要的分区数

先算目标吞吐:

text 复制代码
目标吞吐 = 峰值消息量(条/秒)× 单条处理耗时(秒)= 需要的并行度

举例:

text 复制代码
峰值 5000 条/秒,单条处理 20ms(0.02s)
→ 需要 5000 × 0.02 = 100 个并行处理能力

如果每个消费者线程能跑满,那就需要 100 个分区。

但现实中要考虑两个折扣:

  1. 单线程不可能 100% 跑满(有 IO 等待),实际并行度打个 1.5~2 倍余量;
  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 越贵:

  1. 分配计算更慢:分区多,coordinator 计算分配方案耗时更长;
  2. 停止消费的时间更长:rebalance 期间整个组停止消费,分区越多,恢复后要重新拉取的 offset、要重建的 fetch 请求越多;
  3. 元数据更大:消费者要维护更多分区的状态。

经验值:

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 代价"的三维取舍;而且它只能加不能减,所以第一次定值时就要把未来的峰值算进去。

相关推荐
要开心吖ZSH6 小时前
《通过SeaTunnel 把 MySQL 的数据搬进 Doris》番外篇:容量、切分与速度——Doris 能扛多少,比 MySQL 快多少
mysql·性能优化·doris·分区·分表·分桶
starzy19908 小时前
深度拆解 Kafka 源码:Producer 构造器装配了哪些核心组件
分布式·kafka
我不会起名字3228 小时前
Go 写 Kafka 消费者:sarama 的 3 个坑与优雅退出写法
kafka·消息队列·go·sarama
RobinXYuan2 天前
【Kafka 源码手记】02 Kafka 的核心哲学:Everything Is A Log
kafka·源码阅读
Sayai2 天前
Kafka 2.8→3.9.1 跨大版本升级实战:ZooKeeper 同步升级、数据目录迁移与回滚预案全流程
大数据·运维·分布式·zookeeper·kafka
j7~4 天前
【仿 RabbitMQ 项目】发布订阅消息队列 (篇八):交换机数据管理模块,分层类设计、SQLite 回调原理与代码实现
服务器·网络·数据库·消息队列·交换机·交换机数据管理·sqlite回调原理
guo_wen_qiang4 天前
kafka重复消费
分布式·kafka
ClouGence5 天前
开源数据库管理工具 CloudDM 4.4.0 发布,新增 Kafka、SAP HANA 数据源
数据库·kafka
RobinXYuan5 天前
【Kafka 源码手记】01 从消息队列到 Event Streaming
kafka·源码阅读