bigywater.tech
Kafka 内部原理 · 01

Kafka 为什么要把消息切成 Partition

Kafka 不会把一个 Topic 存成一个不断增长的文件。它把 Topic 切成多个 Partition,每个 Partition 是一段有序、只能追加的日志。这次在的 3 节点 Kafka 4.3.1 集群(KRaft 模式)上创建了 1 个分区和 3 个分区两个 topic,生产、消费、起多消费者,验证 Partition 到底解决了什么问题。

验证:带 key 的消息按 key 分散到不同分区,不是按到达顺序轮询
按 k0→k14 顺序发送的 15 条消息,消费出来的顺序完全按分区分组,不是发送顺序(0,1,2,3...),
验证:同一个 key 反复发送 10 次,全部落在同一个分区
10 条 key 完全相同的消息,全部落在了分区 2,一次例外都没有。

验证:带 key 的消息按 key 分散到不同分区,不是按到达顺序轮询

命令 · shell $ kafka-console-producer.sh --bootstrap-server 127.0.0.1:19192 --topic partition-demo-3p \ --property parse.key=true --property key.separator=: < produce1.txt # produce1.txt 内容: k0:0 k1:1 k2:2 ... k14:14(15 条,key 各不相同,顺序发送) $ kafka-console-consumer.sh --bootstrap-server 127.0.0.1:19192 --topic partition-demo-3p \ --from-beginning --property print.partition=true --property print.key=true --max-messages 15
真实实测 · 本机执行输出
Partition:2	k0	0
Partition:2	k1	1
Partition:2	k8	8
Partition:2	k9	9
Partition:2	k13	13
Partition:0	k2	2
Partition:0	k5	5
Partition:0	k11	11
Partition:0	k12	12
Partition:1	k3	3
Partition:1	k4	4
Partition:1	k6	6
Partition:1	k7	7
Partition:1	k10	10
Partition:1	k14	14

按 k0→k14 顺序发送的 15 条消息,消费出来的顺序完全按分区分组,不是发送顺序(0,1,2,3...),而是 2,2,2,2,2,0,0,0,0,1,1,1,1,1,1 这种按分区聚拢的顺序。15 个不同的 key 分散到了全部 3 个分区(分区 2 拿到 5 条,分区 0 拿到 4 条,分区 1 拿到 6 条),默认分区器是按 key 的哈希值决定目标分区,不是发送顺序或轮询。

验证:同一个 key 反复发送 10 次,全部落在同一个分区

命令 · shell $ kafka-console-producer.sh --bootstrap-server 127.0.0.1:19192 --topic partition-demo-3p \ --property parse.key=true --property key.separator=: < produce2.txt # produce2.txt: fixedkey:round0 ... fixedkey:round9(同一个 key,10 条) $ kafka-console-consumer.sh ... --max-messages 25 # 从全部 25 条消费结果里过滤出 key=fixedkey 的
真实实测 · 本机执行输出
fixedkey 消息出现在的分区: {'Partition:2'}
Partition:2	fixedkey	round0
Partition:2	fixedkey	round1
Partition:2	fixedkey	round2
Partition:2	fixedkey	round3
Partition:2	fixedkey	round4
Partition:2	fixedkey	round5
Partition:2	fixedkey	round6
Partition:2	fixedkey	round7
Partition:2	fixedkey	round8
Partition:2	fixedkey	round9

10 条 key 完全相同的消息,全部落在了分区 2,一次例外都没有。这就是 Kafka 保证"同一个 key 的消息有序"的机制:分区器对 key 做哈希取模,同一个 key 每次都映射到同一个分区号,而单个分区内部的消息是严格按写入顺序追加的——所以只要不换 key,消费端看到的这个 key 的消息顺序就和生产顺序一致。这个顺序保证的范围就是"同一个分区",不是整个 topic。

验证:消费者组的并行度被分区数量卡死

命令 · shell # 场景A:1 个分区的 topic,2 个消费者加入同一个 group $ kafka-console-consumer.sh --bootstrap-server 127.0.0.1:19192 --topic partition-demo-1p --group g1p-demo3 & $ kafka-console-consumer.sh --bootstrap-server 127.0.0.1:19192 --topic partition-demo-1p --group g1p-demo3 & $ kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:19192 --describe --group g1p-demo3 --members # 场景B:3 个分区的 topic,3 个消费者加入同一个 group $ kafka-console-consumer.sh --bootstrap-server 127.0.0.1:19192 --topic partition-demo-3p --group g3p-demo & $ kafka-console-consumer.sh --bootstrap-server 127.0.0.1:19192 --topic partition-demo-3p --group g3p-demo & $ kafka-console-consumer.sh --bootstrap-server 127.0.0.1:19192 --topic partition-demo-3p --group g3p-demo & $ kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:19192 --describe --group g3p-demo --members
真实实测 · 本机执行输出
--- 场景A:1 分区 topic,2 个消费者同组 ---
CONSUMER-ID console-consumer-f2969500...  #PARTITIONS 0   <- 一个partition都没分到,完全空闲
CONSUMER-ID console-consumer-2c7b2b95...  #PARTITIONS 1

--- 场景B:3 分区 topic,3 个消费者同组 ---
CONSUMER-ID console-consumer-724c9f80...  #PARTITIONS 1
CONSUMER-ID console-consumer-b2f1592c...  #PARTITIONS 1
CONSUMER-ID console-consumer-3b91a1c1...  #PARTITIONS 1

场景 A 里,第二个消费者加入了 group,但 kafka-consumer-groups.sh --describe --members 显示它分到了 0 个分区——一个分区在同一时刻只能被同一个 consumer group 里的一个消费者读取,多起的消费者进程完全空闲,不会分到任何数据,也不会报错。场景 B 里 3 个分区对 3 个消费者,做到了一对一分配,每个都拿到恰好 1 个分区。这证明了一件很具体的事:一个 consumer group 内,并行消费的消费者数量上限就是分区数,加分区数量以下的消费者才有意义,加多了会有空转的进程。

结论

Partition 把一个 Topic 拆成多段独立、有序的日志:测出来的三件事——key 决定目标分区、同一个 key 永远落到同一个分区因此保序、以及消费者组的并行度被分区数卡死——合在一起说明了为什么设计上要用 Partition 而不是一个大文件:它是 Kafka 同时兼顾"分区内顺序"和"跨分区并行"的机制,不是任意选择。

分步演示:默认分区器怎么给带 key 的消息选分区

下面每一步的路由结果,都是本文上一节执行 kafka-console-producer.sh/kafka-console-consumer.sh 得到的原始输出,这里只是把它变成了可以逐条回放的动画。

15 条 key 不同的消息,是怎么散到 3 个分区里的
正在发送的这一条已经发送并落分区

    参考与说明

    • 本文全部结果来自本机运行的 3 节点 Kafka 4.3.1 集群(KRaft 模式,无 ZooKeeper),端口 19192/19292/19392;分区分配、消费者组成员这些结果都是执行 kafka-console-producer.sh/kafka-console-consumer.sh/kafka-consumer-groups.sh 得到的原始输出,没有手工改写数字。
    • 没有涉及:默认分区器的具体哈希算法(murmur2)实现细节、分区数量变更(增加分区)对已有 key 路由的影响——这两个话题留给后面单独验证。
    • 没有涉及:副本(Replica)和 ISR 在这次实验里没有单独验证,topic 虽然设置了 3 副本,但本文的重点是分区本身的路由和并行语义,副本相关的故障切换验证放在另一篇里做。
    京ICP备2026067502号-1京公网安备11010502063487号