Kafka分区选举策略

  • 1.分区负载均衡机制
  • 2.副本机制
  • 3.分区Leader选举
  • 4.分区重新分配
  • 5.修改副本因子
  • 6.分区分配策略
  • 6.1 RangeAssignor分配策略
  • 6.2 RoundRobinAssignor分配策略
  • 6.3 StickyAssignor分配策略
  • 6.4 自定义分配策略


1.分区负载均衡机制

Kafka可以将主题 Topic 划分多个分区 Partition ,然后根据一定的分区规则将消息存储到对应的分区中。

如果分区规则设置的合理,那么所有的消息将会被均匀的分配到不同的分区中,这样就是实现了负载均衡和水平扩展。此外,多个订阅者可以从一个或者多个分区中同时消费数据,以支撑海量数据处理能力。

由于消息是顺序追加到分区中的,多个分区顺序写入磁盘的效率要比随机写入内存的效率高很多,这也是Kafka高吞吐量机制的原因之一。

2.副本机制

由于 produceconsumer 都只会与 leader 角色的分区副本 follower 相连,所以Kafka需要以集群的形式来组织、管理这些副本,以达到消息的高可用和持久性。

一个分区可以有多个副本,这些副本保存在不同的 broker 上(如果所有的副本被保存在同一个 broker 上,一旦这个主机挂掉,所有的副本将会丢失)。每个分区的副S本中都只有一个作为 leader ,如果一个 broker 宕机挂掉时, leader 在这台 broker 上的分区会变得不可用,kafka会自动移除这个 leader ,再从其他的 broker 中将 follower 选举为新的 leader

在通常情况下,增加分区可以提高kafka集群的吞吐量,但是如果集群内的分区数过高或者单台服务器的分区数过多的话,也会带来不可以用、高延迟的风险。

集群分区示意图:

kafka controller选举方式 kafka分区选举_apache

3.分区Leader选举

如果某个分区的Leader挂掉了,那么Zookeeper将会在这个Leader的众多follower中重新选举一个新的Leader,然后接下来所有读写操作将会转移到这个新的Leader上。

在Kafka中,并不是采用常见的多数选举的方式进行选举,而是Zookeeper会针对每个Topic维护一个ISR(已同步的副本)集合,显然存在一些没有来得及完全同步数据的副本。只有在这个ISR列表中才有资格成为Leader(先试用ISR集合中的第一个,如果不行则依次类推,因为ISR里面是同步的副本,消息是最完全的且各个节点都是一样的)。

因为存在ISR集合,Kafka需要的冗余度较低,可以容忍的失败数较高,即有较高的容错率。加入某个Topic中存在f + 1个副本,kafka可以容忍 f 个副本不可用。当然如果ISR集合中所有的副本都不可用,Kafka也可以选择在非ISR集合中选举可用的副本,只不过会存在数据不一致等问题。

4.分区重新分配

我们往已经部署好的Kafka集群里面添加机器是最正常不过的需求,而且添加起来非常地方便,我们需 要做的事是从已经部署好的Kafka节点中复制相应的配置文件,然后把里面的broker id修改成全局唯一 的,最后启动这个节点即可将它加入到现有Kafka集群中。

但是问题来了,新添加的Kafka节点并不会自动地分配数据,所以无法分担集群的负载,除非我们新建 一个topic。但是现在我们想手动将部分分区移到新添加的Kafka节点上,Kafka内部提供了相关的工具 来重新分布某个topic的分区。

5.修改副本因子

实际项目中我们可能在创建topic时没有设置好正确的replication-factor,导致kafka集群虽然是高可用 的,但是该topic在有broker宕机时,可能发生无法使用的情况。topic一旦使用又不能轻易删除重建, 因此动态增加副本因子就成为最终的选择。

kafka 1.0版本配置文件默认没有default.replication.factor=x, 因此如果创建topic时,不指定– replication-factor 想, 默认副本因子为1. 我们可以在自己的server.properties中配置上常用的副本因 子,省去手动调整。例如设置default.replication.factor=3。(创建副本因子的时候,副本因子数应该小于等于可用的broker数)

6.分区分配策略

按照Kafka默认的消费逻辑设定,一个分区只能被同一个消费组(ConsumerGroup)内的一个消费者 消费。假设目前某消费组内只有一个消费者C0,订阅了一个topic,这个topic包含7个分区,也就是说 这个消费者C0订阅了7个分区,参考下图

kafka controller选举方式 kafka分区选举_大数据_02


此时消费组内又加入了一个新的消费者C1,按照既定的逻辑需要将原来消费者C0的部分分区分配给消 费者C1消费,消费者C0和C1各自负责消费所分配到的分区,相互之间并无实质性的干 扰。

kafka controller选举方式 kafka分区选举_apache_03


接着消费组内又加入了一个新的消费者C2,如此消费者C0、C1和C2按照上图(3)中的方式各自负责 消费所分配到的分区。

kafka controller选举方式 kafka分区选举_数据_04


如果消费者过多,出现了消费者的数量大于分区的数量的情况,就会有消费者分配不到任何分区。参考 下图,一共有8个消费者,7个分区,那么最后的消费者C7由于分配不到任何分区进而就无法消费任何 消息。

kafka controller选举方式 kafka分区选举_kafka_05


Kafka提供了三种分区分配策略,分别是RangeAssignor分配策略、RoundRobinAssignor分配策略、StickyAssignor分配策略。消费者客户端 参数partition.asssignment.strategy可以配置多个分配策略,彼此之间以逗号分隔。

6.1 RangeAssignor分配策略

RangeAssignor策略的原理是按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按 照跨度进行平均分配,以保证分区尽可能均匀地分配给所有的消费者。对于每一个topic, RangeAssignor策略会将消费组内所有订阅这个topic的消费者按照名称的字典序排序,然后为每个消费 者划分固定的分区范围,如果不够平均分配,那么字典序靠前的消费者会被多分配一个分区。

假设n=分区数/消费者数量,m=分区数%消费者数量,那么前m个消费者每个分配n+1个分区,后面的 (消费者数量-m)个消费者每个分配n个分区。

假设消费组内有2个消费者C0和C1,都订阅了主题t0和t1,并且每个主题都有4个分区,那么所订阅的 所有分区可以标识为:t0p0、t0p1、t0p2、t0p3、t1p0、t1p1、t1p2、t1p3。最终的分配结果为:

消费者C0:t0p0、t0p1、t1p0、t1p1
消费者C1:t0p2、t0p3、t1p2、t1p3

假设上面例子中2个主题都只有3个分区,那么所订阅的所有分区可以标识为:t0p0、t0p1、t0p2、 t1p0、t1p1、t1p2。最终的分配结果为:

消费者C0:t0p0、t0p1、t1p0、t1p1
消费者C1:t0p2、t1p2

缺点:可以明显的看到这样的分配并不均匀,如果将类似的情形扩大,有可能会出现部分消费者过载的情况。

6.2 RoundRobinAssignor分配策略

RoundRobinAssignor策略的原理是将消费组内所有消费者以及消费者所订阅的所有topic的partition按 照字典序排序,然后通过轮询方式逐个将分区以此分配给每个消费者。RoundRobinAssignor策略对应 的partition.assignment.strategy参数值为: org.apache.kafka.clients.consumer.RoundRobinAssignor。

假设消费组中有2个消费者C0和C1,都订阅了主题t0和t1,并且每个主题都有3个分区,那么所订阅的 所有分区可以标识为:t0p0、t0p1、t0p2、t1p0、t1p1、t1p2。最终的分配结果为:

消费者C0:t0p0、t0p2、t1p1
消费者C1:t0p1、t1p0、t1p2

如果同一个消费组内的消费者所订阅的信息是不相同的,那么在执行分区分配的时候就不是完全的轮询 分配,有可能会导致分区分配的不均匀。如果某个消费者没有订阅消费组内的某个topic,那么在分配分 区的时候此消费者将分配不到这个topic的任何分区。

假设消费组内有3个消费者C0、C1和C2,它们共订阅了3个主题:t0、t1、t2,这3个主题分别有1、2、 3个分区,即整个消费组订阅了t0p0、t1p0、t1p1、t2p0、t2p1、t2p2这6个分区。具体而言,消费者 C0订阅的是主题t0,消费者C1订阅的是主题t0和t1,消费者C2订阅的是主题t0、t1和t2,那么最终的分 配结果为:

消费者C1:t1p0
消费者C2:t1p1、t2p0、t2p1、t2p2

可以看到RoundRobinAssignor策略也不是十分完美,这样分配其实并不是最优解,因为完全可以将分 区t1p1分配给消费者C1。

6.3 StickyAssignor分配策略

Kafka从0.11.x版本开始引入这种分配策略,它主要有两个目的:分区的分配要尽可能的均匀; 分区的分配尽可能的与上次分配的保持相同。

当两者发生冲突时,第一个目标优先于第二个目标。鉴于这两个目标,StickyAssignor策略的具体实现 要比RangeAssignor和RoundRobinAssignor这两种分配策略要复杂很多。

假设消费组内有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

假设此时消费者C1脱离了消费组,那么消费组就会执行再平衡操作,进而消费分区会重新分配。如果采 用RoundRobinAssignor策略,那么此时的分配结果如下:

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

如分配结果所示,RoundRobinAssignor策略会按照消费者C0和C2进行重新轮询分配。而如果此时使用 的是StickyAssignor策略,那么分配结果为:

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

可以看到分配结果中保留了上一次分配中对于消费者C0和C2的所有分配结果,并将原来消费者C1的“负 担”分配给了剩余的两个消费者C0和C2,最终C0和C2的分配还保持了均衡。

6.4 自定义分配策略

需实现:org.apache.kafka.clients.consumer.internals.PartitionAssignor

继承自:org.apache.kafka.clients.consumer.internals.AbstractPartitionAssignor