bigywater.tech
Kafka 内部原理 · 03

Producer 一条消息是怎么找到目标 Partition 的

一条消息发出去,最终落在哪个分区,取决于三种情况:有 key(按 key 哈希)、显式指定分区号(跳过一切计算)、都没有(交给分区器决定)。这次用的 Java 客户端、控制批次大小,把"没有 key 时到底怎么选分区"这件事测清楚。

验证:没有 key 时,分区选择跟着"批次"走,不是每条消息都重新计算
发送 60 条不带 key、每条 2KB 的消息,观察到分区号是按"一段一段"分布的:前 16 条全部落在分
验证:显式指定 Partition 会跳过分区器,哪怕没给 key
5 条没有 key 的消息,全部按显式指定的分区号 2 落地,跟粘性分区器当前正处在哪个批次完全无关。

验证:没有 key 时,分区选择跟着"批次"走,不是每条消息都重新计算

命令 · java KafkaProducer<String,String> producer = new KafkaProducer<>(props); // linger.ms=60000, batch.size=16384 String big = "x".repeat(2000); // 每条 2KB List<Future<RecordMetadata>> futures = new ArrayList<>(); for (int i = 0; i < 60; i++) { futures.add(producer.send(new ProducerRecord<>("producer-route-demo4", big))); } producer.flush(); // 收集 60 条各自落在哪个分区
真实实测 · 本机执行输出
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1,1,1]

发送 60 条不带 key、每条 2KB 的消息,观察到分区号是按"一段一段"分布的:前 16 条全部落在分区 0,接下来 16 条全部落在分区 1,再 16 条回到分区 0,最后 12 条又是分区 1——不是随机跳,也不是每条消息单独计算一次。这印证了 Kafka 的粘性分区器(Sticky Partitioner)的行为:同一个批次(batch,这里每 16 条 2KB 消息大致攒满一个 16KB 的 batch.size)里的无 key 消息共享同一个分区,只有当一个批次完成(写满或者 linger 超时)触发换批时,才会重新选一次分区。这样做的好处是减少了请求数量——不用每条消息都单独找目标分区、单独打包。

验证:显式指定 Partition 会跳过分区器,哪怕没给 key

命令 · java // producer.send(new ProducerRecord<>(topic, partition, key, value)) 直接传 partition=2 for (int i = 0; i < 5; i++) { RecordMetadata md = producer.send(new ProducerRecord<>("producer-route-demo4", 2, null, "forced-" + i)).get(); System.out.println("forced partition=" + md.partition()); }
真实实测 · 本机执行输出
forced partition=2
forced partition=2
forced partition=2
forced partition=2
forced partition=2

5 条没有 key 的消息,全部按显式指定的分区号 2 落地,跟粘性分区器当前正处在哪个批次完全无关。ProducerRecord 的构造函数如果传入了 partition 参数,客户端会跳过整个分区选择逻辑(不管是 key 哈希还是粘性分区器),直接把消息交给目标分区所在的 Leader。这是一个明确的优先级:显式 partition > key 哈希 > 粘性分区器兜底。

结论

一条无 key 消息该走哪个分区,答案不是"轮询"也不是"每条随机",而是"跟着当前这个批次走"——测出批次内 16 条消息共享同一个分区、批次完成后才换下一个分区。显式指定分区号则完全绕开这套逻辑,优先级最高。理解这个机制,对调优 batch.size/linger.ms 和判断"消息为什么都堆在一个分区"这种问题很关键——不是负载不均的 bug,是同一批次内本来就该在一起。

分步演示:60 条无 key 消息,是怎么按批次分布到分区的

下面的分区分布数据,就是本文上一节运行 Java 客户端得到的原始结果 [0×16, 1×16, 0×16, 1×12],这里按批次回放。

没有 key 的消息,是跟着"批次"换分区的,不是逐条计算
刚攒满发送的这一批更早的批次

    参考与说明

    • 本文结果来自的 Java KafkaProducer 客户端(连接 3 节点集群),批次归属靠设置 linger.ms=60000(避免超时提前切批)、batch.size=16384、每条消息 2KB 负载来控制;early 版本的测试(小消息、小批量、producer.flush() 强制切批)没有观察到明显的分区切换,说明批次真的要写到接近 batch.size 才会触发换分区,不是任意的时间片轮转,这个边界也是测出来的,不是文档直接抄的。
    • 和《Kafka 为什么要把消息切成 Partition》一起看:那篇验证了有 key 时的路由和顺序保证,这篇补上了没有 key 时的路由机制,两篇合起来覆盖了 Producer 端路由的完整语义。
    • 没有涉及:自定义 Partitioner(实现 org.apache.kafka.clients.producer.Partitioner 接口)的编写和验证,这个留给后面需要自定义路由逻辑的场景再单独写。
    京ICP备2026067502号-1京公网安备11010502063487号