3. 默认分区策略(DefaultPartitioner):轮询与粘性分区,适用场景与局限性

说实话,很多刚接触Kafka的同学,上来就问我:「分区策略到底怎么选?」

我的回答通常是:先搞懂默认的。你连系统自带的DefaultPartitioner都没吃透,就别急着去搞什么自定义分区器了。

今天我们就来聊聊这个默认分区策略。它其实包含两种模式:轮询(Round-Robin)粘性分区(Sticky Partitioner)。嗯,这里要注意,Kafka 2.4版本之后,默认行为已经变了。

3.1 轮询分区:最朴素的负载均衡

轮询策略,说白了就是「轮流来」。

假设你有3个分区,第一条消息去分区0,第二条去分区1,第三条去分区2,第四条又回到分区0。就这么简单粗暴。

核心逻辑:消息按顺序均匀分配到所有可用分区,保证每个分区接收的消息数量大致相等。

我早期做日志采集系统时,用的就是纯轮询。那时候数据量不大,每个分区的消费速度也差不多,轮询确实省心。

但轮询有个明显的短板——批量效率低。为什么呢?

你想想看,轮询模式下,每条消息都可能发往不同的分区。这意味着Producer需要为每条消息单独创建一个批次(Batch)。如果消息体很小,比如就几百字节,那网络开销占比就很高了。

// 轮询模式下的伪代码示意
for (Message msg : messages) {
    int partition = counter++ % partitionCount;
    producer.send(new ProducerRecord<>(topic, partition, msg.key(), msg.value()));
}

这段代码看着简单,但实际跑起来,吞吐量往往上不去。我在项目中遇到过类似场景,单机TPS死活到不了10万,后来发现是轮询导致批次太小,频繁刷盘。

3.2 粘性分区:性能优化的产物

Kafka 2.4版本引入了粘性分区策略,现在已经是默认选项了。

它的思路很巧妙:先尽量往一个分区里塞消息,攒够一批再换下一个

我的理解:粘性分区就像你去食堂打饭,先在一个窗口排着,等打完了再换另一个窗口。而不是每个窗口排一次队,打一粒米。

这样做的好处很明显:

  • 批次更大:同一个分区的消息可以合并成更大的Batch,减少网络往返次数
  • 吞吐量更高:实测下来,粘性分区比纯轮询能提升30%-50%的吞吐量
  • 延迟更低:因为批次更容易填满,不需要等待超时
// 粘性分区策略的核心思想(简化版)
// 1. 选择一个分区作为"粘性"目标
// 2. 持续向该分区发送消息,直到批次满或达到linger.ms
// 3. 切换下一个分区

public int partition(String topic, byte[] key, byte[] value, Cluster cluster) {
    // 实际源码中会维护一个粘性分区缓存
    // 这里只展示逻辑
    if (currentBatch.isFull() || isTimeout()) {
        stickyPartition = nextPartition(cluster);
    }
    return stickyPartition;
}

我曾经帮一个金融客户调优Kafka性能,他们用的是老版本,轮询策略下吞吐量只有8MB/s。升级到2.4之后,什么都没改,直接飙到15MB/s。嗯,这就是粘性分区的威力。

3.3 适用场景分析

两种策略各有各的用武之地。我整理了一张表,方便你对照:

场景 推荐策略 原因
高吞吐写入(>10MB/s) 粘性分区(默认) 批次大,网络利用率高
消息体极小(<100字节) 粘性分区 减少小消息的网络开销
严格均匀分布 轮询 粘性分区在切换前会有短暂倾斜
金融对账/流水场景 自定义分区(按key) 需要保证同一订单的消息有序
测试环境/低负载 两者均可 差异不大,用默认就行
避坑指南:我曾经遇到一个案例,某支付系统用默认的粘性分区处理交易流水。结果某个分区的消息积压了,导致该分区上的所有交易延迟飙升。原因就是粘性分区在切换前,所有消息都往一个分区怼,一旦那个分区出问题,影响面就大了。

3.4 局限性:没有银弹

默认分区策略虽然好用,但也不是万能的。我总结了几个常见的坑:

  • 无法保证消息顺序:如果你需要同一业务ID的消息进入同一个分区,默认策略做不到。必须用自定义分区器,按key哈希。
  • 粘性分区有短暂倾斜:在切换分区之前,所有消息都集中在一个分区上。虽然整体均衡,但短时间内的负载是不均匀的。
  • 无法感知消费者负载:默认策略只考虑分区数量,不考虑消费者的消费能力。如果某个消费者处理慢,它分配到的分区依然会收到同样多的消息。
  • 不适合超大消息体:如果单条消息就几MB,粘性分区的批次优势就不明显了。反而可能因为批次太大导致内存压力。

说白了,默认分区策略是「通用方案」,不是「最优方案」。它适合大多数场景,但如果你有特殊需求,还是得自己动手。

3.5 知识体系总览

下面这张图,帮你理清默认分区策略的核心脉络:

默认分区策略(DefaultPartitioner)知识体系 DefaultPartitioner 轮询(Round-Robin) 粘性分区(Sticky) 均匀分配,每条消息独立 批次小,网络开销高 攒批发送,吞吐量高 短暂负载倾斜 适用:高吞吐通用场景 | 局限:无法保证顺序、短暂倾斜

3.6 我的建议

如果你刚开始接触Kafka,或者你的业务没有特殊的分区需求,直接用默认的粘性分区策略就好。别折腾。

但如果你遇到以下情况,就得考虑换策略了:

  • 业务要求同一用户ID的消息必须有序 → 按key哈希分区
  • 某些分区的消费者处理能力明显弱于其他 → 自定义分区或调整消费者
  • 消息体极大(>1MB)且对延迟敏感 → 考虑减少批次大小或换策略
一个小技巧:我习惯在压测时先跑一轮默认策略,看看吞吐量和延迟的基线。如果基线不达标,再考虑优化。很多时候,问题根本不在分区策略上,而是Producer的buffer.memory或acks配置没调好。

好了,默认分区策略就聊到这儿。记住一句话:默认的,不一定是最好的,但一定是最稳妥的起点


公众号:蓝海资料掘金营,微信deep3321