分区

分区概述

主题划分为多个分区,根据分区规则将消息存到不同分区。配置合理的分区规则, 实现负载均衡和水平扩展。

多个订阅者可以从一个或多个分区同时消费数据,以支撑海量数据处理。

消息是追加到分区的,多个分区顺序写磁盘的效率比随机写内存效率高,是kafka高吞吐保证。

副本机制

Producer和Consumer都只会与Leader角色的分区副本相连,所以kafka需要以集群的组织形式提供主题下的消息高可用。kafka支持主备复制,所以消息具备高可用和持久性。

一个分区有多个副本,这些副本保存在不同broker上。每个分区的副本中都会有一个作为Leder。当一个broker失败时,Leader在这台broker上的分区都会变得不可用。kafaka会自动转移Leader,再从其他副本中选一个新的Leader。

通常情况下,增加分区可以提供kafka集群的吞吐量,但应该意识到集群的总分区数或是单台服务器上的分区数过多时,会增加不可用及延迟的风险。

红色:leader

绿色:flower

Leader选举

会在Zookeeper上针对每个topic维护一个称为ISR(in-sync-replicat,已同步的副本)集合,显然还有其他一些副本未来得及同步。

只有这个ISR里面的才有资格成为Leader(先使用ISR里面的第一个,如果不行以此类推,因为ISR里都是同步副本,消息最完整且各个几点是一样的)。

通过ISR,kafka需要的冗余度较低,可容忍的失败数比较高。假设topic有n+1个副本,kafka可以容忍n个不可用。当然如果,ISR里都不可用,也可以选择其他副本,只是数据不一致

分区重新分配

集群里添加broker,复制配置文件,修改broker id为唯一,最后启动节点将它加入集群中。

但是新的kafka节点并不会自动分配数据,所以无法分担集群负载,除非新建topic。

我们想手动将部分分区移到新添加的kafka节点上,kafka提供相关工具来重新分布某个topic分区。

集群配置
配置文件
  • 复制出多个kafka目录,构成集群
  • 修改每个kafka中config/server.properties
    • broker.id:修改为唯一
    • listeners:修改端口为位移
    • port:修改为唯一
    • log.dirs=/opt/kafka/kafka-01/logs依此修改每个kafka日志路径
    • 清除复制过来的日志 rm -rf logs/*
创建主题
#创建主题,3个分区,每个分区3个副本
./kafka-topics.sh --zookeeper 192.168.0.191:2181 --create --topic jpy-par --partitions 3 --replication-factor 3

#查看主题,分区0,leader在broke.id=1上,isr=1,0,2,0和2是fllower,
#主题是在新增节点之后创建,所以负载均衡了
[root@localhost bin]# ./kafka-topics.sh --zookeeper 192.168.0.191:2181 --describe --topic jpy-par
Topic: jpy-par	PartitionCount: 3	ReplicationFactor: 3	Configs: 
	Topic: jpy-par	Partition: 0	Leader: 1	Replicas: 1,0,2	Isr: 1,0,2
	Topic: jpy-par	Partition: 1	Leader: 2	Replicas: 2,1,0	Isr: 2,1,0
	Topic: jpy-par	Partition: 2	Leader: 0	Replicas: 0,2,1	Isr: 0,2,1
新增分区
#添加分区
./kafka-topics.sh --zookeeper 192.168.0.191:2181 --alter --topic jpy-par --partitions 4

#查看主题,节点1上有2个leader,分担了分区0和3的数据,负载不均衡
[root@localhost bin]# ./kafka-topics.sh --zookeeper 192.168.0.191:2181 --describe --topic jpy-par
Topic: jpy-par	PartitionCount: 4	ReplicationFactor: 3	Configs: 
	Topic: jpy-par	Partition: 0	Leader: 1	Replicas: 1,0,2	Isr: 1,0,2
	Topic: jpy-par	Partition: 1	Leader: 2	Replicas: 2,1,0	Isr: 2,1,0
	Topic: jpy-par	Partition: 2	Leader: 0	Replicas: 0,2,1	Isr: 0,2,1
	Topic: jpy-par	Partition: 3	Leader: 1	Replicas: 1,2,0	Isr: 1,2,0
添加节点

添加新节点(复制文件,修改properties)之后查看主题,可见新添加的节点并没有分配之前主题的分区

[root@localhost bin]# ./kafka-topics.sh --zookeeper 192.168.0.191:2181 --describe --topic jpy-par
Topic: jpy-par	PartitionCount: 4	ReplicationFactor: 3	Configs: 
	Topic: jpy-par	Partition: 0	Leader: 1	Replicas: 1,0,2	Isr: 1,0,2
	Topic: jpy-par	Partition: 1	Leader: 2	Replicas: 2,1,0	Isr: 2,1,0
	Topic: jpy-par	Partition: 2	Leader: 0	Replicas: 0,2,1	Isr: 0,2,1
	Topic: jpy-par	Partition: 3	Leader: 1	Replicas: 1,2,0	Isr: 1,2,0
重新分配
  1. 定义reassign.json文件,确定要重启分配分区的主题
{"topics":[{"topic":"jpy-par"}],
 "version":1
 }
  1. 使用kafka-reassign-partitions.sh工具生成reassign plan
#生成执行方案,generate
bin/kafka-reassign-partitions.sh --zookeeper 192.168.0.191:2181 --topics-to-move-json-file reassign.json --broker-list "0,1,2,3" --generate

#输出
Current partition replica assignment
{"version":1,"partitions":[{"topic":"jpy-par","partition":0,"replicas":[1,0,2],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":1,"replicas":[2,1,0],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":2,"replicas":[0,2,1],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":3,"replicas":[1,2,0],"log_dirs":["any","any","any"]}]}

Proposed partition reassignment configuration
{"version":1,"partitions":[{"topic":"jpy-par","partition":0,"replicas":[1,0,2],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":1,"replicas":[2,1,3],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":2,"replicas":[3,2,0],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":3,"replicas":[0,3,1],"log_dirs":["any","any","any"]}]}

第一个json:当前分区副本分配情况

第二个json:重新分配候选方案,只是方案,并未执行

  1. 将第二个json保存在result.json中

  2. 执行分配策略

#执行,需要时间,--execute
bin/kafka-reassign-partitions.sh --zookeeper 192.168.0.191:2181 --reassignment-json-file result.json --execute
#输出当前分配策略,不是执行之后的
Current partition replica assignment
{"version":1,"partitions":[{"topic":"jpy-par","partition":0,"replicas":[1,0,2],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":1,"replicas":[2,1,0],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":2,"replicas":[0,2,1],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":3,"replicas":[1,2,0],"log_dirs":["any","any","any"]}]}

Save this to use as the --reassignment-json-file option during rollback
Successfully started partition reassignments for jpy-par-0,jpy-par-1,jpy-par-2,jpy-par-3


#查看执行情况,verify
bin/kafka-reassign-partitions.sh --zookeeper 192.168.0.191:2181 --reassignment-json-file result.json --verify
#输出
Status of partition reassignment:
Reassignment of partition jpy-par-0 is complete.
Reassignment of partition jpy-par-1 is complete.
Reassignment of partition jpy-par-2 is complete.
Reassignment of partition jpy-par-3 is complete.
Clearing broker-level throttles on brokers 0,1,2,3
Clearing topic-level throttles on topic jpy-par


#再次查看主题详情,出现了问题,0节点还是没有作为leadr,于是手动创建执行计划,参考下面技巧
bin/kafka-topics.sh --zookeeper 192.168.0.191:2181 --describe --topic jpy-par
#输出
Topic: jpy-par	PartitionCount: 4	ReplicationFactor: 3	Configs: 
	Topic: jpy-par	Partition: 0	Leader: 1	Replicas: 1,0,2	Isr: 2,1,0
	Topic: jpy-par	Partition: 1	Leader: 2	Replicas: 2,1,3	Isr: 2,1,3
	Topic: jpy-par	Partition: 2	Leader: 3	Replicas: 3,2,0	Isr: 2,0,3
	Topic: jpy-par	Partition: 3	Leader: 1	Replicas: 0,3,1	Isr: 1,0,3
技巧
  • 使用 kafka-reassign-partitions.sh 工具生成的reassign plan只是一个建议,方便大家而已。自己完全可以编辑一个reassign plan,然后执行它
{"version":1,"partitions":[{"topic":"jpy-par","partition":0,"replicas":[0,1,2],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":1,"replicas":[1,2,3],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":2,"replicas":[2,3,0],"log_dirs":["any","any","any"]},{"topic":"jpy-par","partition":3,"replicas":[3,0,1],"log_dirs":["any","any","any"]}]}
  • verfiy查看执行情况时,可以在语句后面加上 | grep -c "progress"就知道有多少分区还没完成,输出为0的时候就是完成了。

修改副本因子

  • 不能通过kafka-topoics.sh来修改副本数

  • 通过kafka-reassign-partitions.sh,配置配置topic的副本,保存为json文件

    • 配置副本json文件
    {“version”:1,
    “partitions”:[
    {“topic”:”yqtopic01″,”partition”:0,”replicas”:[0,1,2]},
    {“topic”:”yqtopic01″,”partition”:1,”replicas”:[0,1,2]},
    {“topic”:”yqtopic01″,”partition”:2,”replicas”:[0,1,2]}
    ]}
    
    • 执行脚本
    ./kafka-reassign-partitions.sh -zookeeper 127.0.0.1:2181 –reassignment-json-file result.json –execute
    

分区分配策略

多个策略以逗号隔开。

分配分区的条件:

  • 同一个消费组内消费者的新增、关闭或崩溃

  • 订阅的主题新增分区。

RangeAssignor(默认)

对每个主题而言,先按照分区序号排序,然后将消费者排序。分区数/消费者数=n,分区数%消费者数=m,前m个消费者每个分配n+1分区,后面的每个消费者分配n个分区

  1. 例:2个消费者C0,C1,都订阅了主题t0,t1,每个主题4个分区。那所有分区为:t0p0、t0p1、t0p2、t0p3、t1p0、t1p1、t1p2、t1p3

    最终分配结果:4/2=2,4%2=0,每个消费这分配2个分区(针对每个主题而言)

    #分配均匀
    消费者C0:t0p0、t0p1、t1p0、t1p1
    消费者C1:t0p2、t0p3、t1p2、t1p3
    
  2. 例:假设每个主题3个分区,那所有分区为:t0p0、t0p1、t0p2、t1p0、t1p1、t1p2,前1个消费者分配2个分区,后面的分配1个分区,分配结果:

    #分配不均
    C0:t0p0、t0p1、t1p0、t1p1
    C1:t0p2、t1p2
    

总结:如果将类似的情形扩大,有可能会出现部分消费者过载的情况

RoundRobinAssignor

将消费组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序,然后通过轮询方式逐个将分区以此分配给每个消费者

  1. 同一个消费组内所有的消费者的订阅信息都是相同的,那此策略的分区分配会是均匀的。

    例:2个消费者C0,C1,都订阅了主题t0和t1,每个主题都有3个分区,那所有分区为:t0p0、t0p1、t0p2、t1p0、t1p1、t1p2,分配结果

    #分配均匀
    消费者C0:t0p0、t0p2、t1p1
    消费者C1:t0p1、t1p0、t1p2
    
  2. 同一个消费组内的消费者所订阅的信息是不相同的,分配的时候就不是完全的轮询分配,有可能会导致分区分配的不均匀。

    例:3个消费者C0,C1,C2,共同订阅了主题t0,t1,t2,分别有1,2,3个分区,整个消费组订阅了t0p0、t1p0、t1p1、t2p0、t2p1、t2p2这6个分区。

    具体而言:C0订阅t0,C1订阅t0和t1,C2订阅t0,t1和t2,分配结果:

    #分配不均
    消费者C0:t0p0
    消费者C1:t1p0
    消费者C1:t1p1、t2p0、t2p1、t2p2
    

总结:RoundRobinAssignor策略也不完美,完全可以将t1p1分配给C1

StickyAssignor

它主要有两个目的:1. 分区的分配要尽可能的均匀;2. 分区的分配尽可能的与上次分配的保持相同。

当两者发生冲突时,第一个目标优先于第二个目标。

  1. 假设消费组内有3个消费者:C0、C1和C2,都订阅了4个主题:t0、t1、t2、t3,并且每个主题有2个分区,整个消费组订阅了t0p0、t0p1、t1p0、t1p1、t2p0、t2p1、t3p0、t3p1这8个分区。最终的分配结果如下:

    消费者C0:t0p0、t1p1、t3p0
    消费者C1:t0p1、t2p0、t3p1
    消费者C2:t1p0、t2p1
    

    这样初看上去似乎与采用RoundRobinAssignor策略所分配的结果相同,但事实是否真的如此呢?再假设此时消费者C1脱离了消费组,那么消费组就会执行再平衡操作,进而消费分区会重新分配。

    • 如果采用RoundRobinAssignor策略,分配结果如下:

      #重新轮询分配
      消费者C0:t0p0、t1p0、t2p0、t3p0
      消费者C2:t0p1、t1p1、t2p1、t3p1
      

      总结:所有分区按照C0,C2重新轮询

    • 如果采用StickyAssignor策略,分配结果如下:

      消费者C0:t0p0、t1p1、t3p0、t2p0
      消费者C2:t1p0、t2p1、t0p1、t3p1
      

      总结:保留了上一次分配中对于消费者C0和C2的所有分配结果,将C1的负担分配给了C0和C2(去掉C1之后,C0本来分配了3个,C2分配2个,所有按从C2开始轮询分配)

自定义分配策略

实现PartitionAssignor

继承AbstractPartitionAssignor

posted @   jpy  阅读(58)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示