分区使用多副本机制来提高可靠性,但只有 leader 副本对外提供读写服务,而 follower 副本只是对内部消息进行同步。如果一个分区的 leader 副本不可用,那就意味着整个分区不可以用,此时就需要从 follower 副本重新选举一个 leader 副本继续对外提供服务。从某种角度讲副本的数量决定了这个节点负载的高低。

在创建主题的时候,副本会均衡的分布到每个broker。针对同一个分区而言,一个borker只能有分区的一个副本。当某个broker宕机那么该分区就会启动别的broker的副本为leader副本,这样一来就会导致集群的负载不均衡,影响系统的健壮性和稳定性。原来的 leader 节点恢复之后重新加入集群时,它只能成为一个新的 follower 节点而不再对外提供服务。
比如刚创建主题,分区均衡分布在每个节点,但是当把broker2节点停掉,那么原先分区1的leader节点会变为0,此时 broker2负载最低而broker0负载最高。

# bin/kafka-topics.sh --zookeeper localhost:2181/ kafka --describe --topic topic-partitions
	Topic:topic-partitions	PartitionCount:3	ReplicationFactor:3	Configs:
	Topic: topic-partitions	Partition: 0	Leader: 1	Replicas: 1,2,0	Isr: 1,2,0
	Topic: topic-partitions	Partition: 1	Leader: 2	Replicas: 2,0,1	Isr: 2,0,1
	Topic: topic-partitions	Partition: 2	Leader: 0	Replicas: 0,1,2	Isr: 0,1,2
# bin/kafka-topics.sh --zookeeper localhost:2181/ kafka --describe --topic topic-partitions
Topic:topic-partitions	PartitionCount:3	ReplicationFactor:3	Configs: 
    Topic: topic-partitions	Partition: 0	Leader: 1	Replicas: 1,2,0	Isr: 1,0,2
    Topic: topic-partitions	Partition: 1	Leader: 0	Replicas: 2,0,1	Isr: 0,1,2
    Topic: topic-partitions	Partition: 2	Leader: 0	Replicas: 0,1,2	Isr: 0,1,2

为了能够有效治理失衡的情况,kafka 引入了优先副本的概念。比如分区0的AR集合为[1,2,0],那么分区0的优先副本就是1,理想情况下优先副本就是leader副本。Kafka 要保证所有的优先副本在 Kafka 集群均衡分布,这样就保证了所有分区的 leader 均衡分布。如果 leader 分布过于集中,就会造成集群负载不均衡。

所谓的优先副本就是指通过一定的方式促使优先副本选举为 leader 副本,以此来促进集群的负载均衡,也可以称为“分区平衡”。但是,分区平衡并不意味着集群的负载均衡。因为每个分区的leader副本的负载是不一样的,有些leader副本的负载很高,比如需要承载 TPS 为 30000的负荷,而有的leader只需要个位数的负荷。也就是说,就算集群中的分区分配均衡、leader 分配均衡,也并不能确保整个集群的负载就是均衡的,还需要其他一些硬性的指标来做进一步的衡量,这个会在后面的章节中涉及,本节只探讨优先副本的选举。

在 Kafka 中可以提供分区自动平衡的功能,与此对应的 broker 端参数是 auto.leader. rebalance.enable,此参数的默认值为 true,即默认情况下此功能是开启的。如果开启分区自动平衡的功能,则 Kafka 的控制器会启动一个定时任务,这个定时任务会轮询所有的 broker 节点,计算每个 broker 节点的分区不平衡率(broker 中的不平衡率=非优先副本的 leader 个数/分区总数)是否超过 leader.imbalance.per.broker.percentage 参数配置的比值,默认值为10%,如果超过设定的比值则会自动执行优先副本的选举动作以求分区平衡。执行周期由参数 leader.imbalance.check.interval.seconds 控制,默认值为300秒,即5分钟。

不过在生产环境中不建议将 auto.leader.rebalance.enable 设置为默认的 true,因为这可能引起负面的性能问题,也有可能引起客户端一定时间的阻塞。因为执行的时间无法自主掌控,如果在关键时期(比如电商大促波峰期)执行关键任务的关卡上执行优先副本的自动选举操作,势必会有业务阻塞、频繁超时之类的风险。前面也分析过,分区及副本的均衡也不能完全确保集群整体的均衡,并且集群中一定程度上的不均衡也是可以忍受的,为防止出现关键时期“掉链子”的行为,笔者建议还是将掌控权把控在自己的手中,可以针对此类相关的埋点指标设置相应的告警,在合适的时机执行合适的操作,而这个“合适的操作”就是指手动执行分区平衡。

Kafka中 kafka-perferred-replica-election.sh 脚本提供了对分区 leader 副本进行重新平衡的功能。优先副本的选举过程是一个安全过程,Kafka 客户端可以自动感知分区leader副本的变更。

bin/kafka-preferred-replica-election.sh --zookeeper localhost:2181/kafka
 
Created preferred replica election path with topic-demo-3,__consumer_offsets-22, topic-config-1,__consumer_offsets-30,__bigdata_monitor-12,__consumer_offsets-8,__consumer_offsets-21,topic-create-0,__consumer_offsets-4,topic-demo-1,topic-partitions-1,__consumer_offsets-27,__consumer_offsets-7,__consumer_offsets-9,__consumer_offsets-46,(…省略若干)

[root@node1 kafka_2.11-2.0.0]# bin/kafka-topics.sh --zookeeper localhost:2181/ kafka --describe --topic topic-partitions
Topic:topic-partitions	PartitionCount:3	ReplicationFactor:3	Configs: 
    Topic: topic-partitions	Partition: 0	Leader: 1	Replicas: 1,2,0	Isr: 1,0,2
    Topic: topic-partitions	Partition: 1	Leader: 2	Replicas: 2,0,1	Isr: 0,1,2
    Topic: topic-partitions	Partition: 2	Leader: 0	Replicas: 0,1,2	Isr: 0,1,2

可以看到在脚本执行之后,主题 topic-partitions 中的所有 leader 副本的分布已经和刚创建时的一样了,所有的优先副本都成为 leader 副本。
上面的示例会将集群所有的分区都执行一遍优先副本的选举。分区数越多打印的信息也就越多。如果要执行的分区数很多,那么势必会对客户端造成影响。如果集群的分区数过多还会执行失败,因为在选举过程中具体的元数据会被存入 Zookeeper的/admin/preferred_replica_election 节点,默认情况 Zookeeper 允许的节点数据大小为1MB。如果元数据大小超出默认的大小就会失败。

kafka-perferred-replica-election.sh 脚本还提供了 path-to-json-file 参数来小批量的对部分分区执行优先副本的选举操作。通过该参数指定一个JSON文件,文件内容如下

{
        "partitions":[
                {
                        "partition":0,
                        "topic":"topic-partitions"
                },
                {
                        "partition":1,
                        "topic":"topic-partitions"
                },
                {
                        "partition":2,
                        "topic":"topic-partitions"
                }
        ]
}
# bin/kafka-preferred-replica-election.sh --zookeeper localhost:2181/kafka --path-to-json-file election.json
Created preferred replica election path with topic-partitions-0,topic-partitions-1, topic-partitions-2
Successfully started preferred replica election for partitions Set(topic- partitions-0, topic-partitions-1, topic-partitions-2)

# bin/kafka-topics.sh --zookeeper localhost:2181/ kafka --describe --topic topic-partitions
Topic:topic-partitions	PartitionCount:3	ReplicationFactor:3	Configs:
    Topic: topic-partitions	Partition: 0	Leader: 1	Replicas: 1,2,0	Isr: 1,0,2 
    Topic: topic-partitions	Partition: 1	Leader: 2	Replicas: 2,0,1	Isr: 0,1,2
    Topic: topic-partitions	Partition: 2	Leader: 0	Replicas: 0,1,2	Isr: 0,1,2

在实际生产环境中,一般使用 path-to-json-file 参数来分批、手动地执行优先副本的选举操作。尤其是在应对大规模的 Kafka 集群时,理应杜绝采用非 path-to-json-file 参数的选举操作方式。同时,优先副本的选举操作也要注意避开业务高峰期,以免带来性能方面的负面影响。