bigywater.tech
Kafka 内部原理 · 23

一个 Consumer 能不能消费多个 Partition

可以,而且这是分区数多于消费者数时的常态:一个消费者会同时持有多个分区。真正的问题是它读到的顺序是什么样的。这次往一个 6 分区的 topic 里按序号 0 到 599 依次发送 600 条消息(37 个不同的 key),再用同一个消费组里的单个 Java Consumer 读回来,检查它持有哪些分区、消息以什么顺序到达,以及"发送顺序"和"消费顺序"到底还一不一致。

验证:单个消费者持有全部 6 个分区,每个分区内部严格有序
只有一个消费者的组里,它自动持有了全部 6 个分区。
验证:跨分区没有全局顺序,一个分区读完才轮到下一个
第二次运行(另起一个新的消费组,从头读同一批 600 条)里,这个消费者先把分区 2 的 179 条整块读完,然后是分区

验证:单个消费者持有全部 6 个分区,每个分区内部严格有序

命令 · java // topic multi-part-demo: 6 分区、3 副本。Producer 依次发送 value=0..599,key = "key-" + (i % 37) // 单个 Consumer:subscribe 后打印 assignment,记录每条的 (partition, offset, 发送序号)
真实实测 · 本机执行输出
assignment = [0, 1, 2, 3, 4, 5]
消费到 600 条,poll 次数 8,单次 poll 最多同时包含 4 个分区
各分区条数 = [49, 162, 179, 97, 64, 49]
分区内 offset/发送顺序 不递增的次数 = 0

只有一个消费者的组里,它自动持有了全部 6 个分区。600 条全部读到,poll 8 次;单次 poll() 返回的批次最多同时含 4 个分区的数据。每个分区内部,消费顺序的 offset 和当初的发送序号都严格递增,不递增的次数是 0。各分区的条数很不均匀(49 到 179):这是 37 个 key 经过哈希分到 6 个分区的结果,key 的个数太少,分布不均,与消费者无关。

验证:跨分区没有全局顺序,一个分区读完才轮到下一个

命令 · java // 记录到达顺序,统计: // 1) 每条消息相对已读到的最大发送序号是否"更晚才读到" // 2) 按到达顺序把连续来自同一分区的记录压缩成段
真实实测 · 本机执行输出
第一次运行,前 24 条 (分区:发送序号) = 0:1 0:17 0:19 0:38 0:54 0:56 0:75 0:91 0:93 0:112 ... 0:278
第二次运行(新消费组重新读同一批数据),前 24 条 = 2:2 2:5 2:6 2:10 2:12 2:23 2:24 2:25 2:30 ...

连续来自同一分区的最长一段 = 179
乱序条数 = 420 / 600
按到达顺序压缩后的分区段 = p2x179 p3x97 p1x162 p5x49 p0x49 p4x64

第二次运行(另起一个新的消费组,从头读同一批 600 条)里,这个消费者先把分区 2 的 179 条整块读完,然后是分区 3 的 97 条、分区 1 的 162 条、分区 5、分区 0、分区 4,每个分区的数据几乎是一整块连续到达的。发送序号是 0 到 599 依次递增,所以有 420 条消息(70%)是在比它更晚发送的消息之后才被读到的。两次运行的分区顺序也不一样:第一次先读分区 0,第二次先读分区 2。所以"同一个消费者消费多个分区"能保证的只有分区内顺序;跨分区的先后顺序既不等于发送顺序,也不是稳定的。这里我把整批数据放在一个 topic 里静态读取,到达顺序的块状特征在有持续新消息写入时会更混杂,这次没有测持续写入的场景。

结论

一个消费者可以同时消费多个分区,分区数多于消费者数时这是必然情况。它读到的数据满足"每个分区内按 offset 递增、与发送顺序一致",但跨分区没有任何顺序保证:本次实验里 600 条消息有 420 条出现了"比它更晚发送的消息先被读到",而且每次运行分区的读取先后可能不同。业务上需要严格先后关系的消息,应该用同一个 key 让它们进入同一个分区。

分步演示:跨分区的到达顺序,长什么样

每一块的分区号和条数,都是本文上面一节运行Java Consumer得到的原始到达顺序。

600条按顺序发送的消息,是一整块一整块按分区到达的,不是交替读取
刚到达的这一整块更早到达的块

    参考与说明

    • 数据来自本机 3 节点 Kafka 4.3.1 集群,topic 6 分区 3 副本,600 条消息,两次运行使用不同的 group.id(每次 mp- 加纳秒时间戳)。第一次运行同时负责写入数据,第二次只读。第二次的最后四行统计是我加代码之后新增的,所以"乱序条数"和"分区段"只有第二次的数字。
    • 和《Consumer Group:多个消费者如何分摊 Partition》一起看:那篇看的是多个消费者如何分分区,这篇看的是一个消费者拿到多个分区之后的读取顺序。
    • 和《Kafka 为什么要把消息切成 Partition》一起看:那篇验证了同 key 落在同一分区,是这里"用 key 保证顺序"的前提。
    • 没有验证:max.poll.records、max.partition.fetch.bytes 这类参数对多分区读取公平性的影响;有持续写入时的到达顺序;pause()/resume() 单个分区。
    京ICP备2026067502号-1京公网安备11010502063487号