KAFKA进阶:【十四】能否说一下kafka的负载均衡机制?

大家好,这是一个为了梦想而保持学习的博客。这个专题会记录我对于KAFKA的学习和实战经验,希望对大家有所帮助,目录形式依旧为问答的方式,相当于是模拟面试。


一、概述

对于消息中间件来说,负载均衡是非常重要的,不能说我部署了10台机器,结果就2台忙的要死而其余8台都非常空闲,所以通常都需要一些机制来保障集群的负载相对均衡。
注意这里说的是**保障而不是保证**,因为这些机制可以尽可能的去保障整体负载相对均衡,但是没办法做到绝对的保证负载均衡。


二、服务端负载均衡取决于什么?是如何保障负载均衡的?

我们从前面的文章可以知道,生产/消费的真正实体是对应的分区的leader,所以服务端的负载是否均衡就基本上取决于leader分布的是否均匀。

那么服务端是如何尽可能的让leader分布均匀的呢?
首先我们需要了解在创建topic的时候分区副本是如何分布的,然后再了解是如何从这些副本中选出leader的。
我们先来看看topic创建时,分区副本的分布过程是什么样子的,我们假设创建一个TopicA,有5分区,3副本,如下图所示:
过程概述:
1、随机挑选一个startIndex,图示中挑选的startIndex = 0
2、从startIndex开始按照 broker顺序 生成对应的第一个副本。
3、然后下一轮从startIndex + 1开始顺序生成第二个副本,以此类推。

这里可能有同学有疑问,为什么是随机挑选一个startIndex呢?我们试想一下要是每次创建分区副本的时候都指定一个特定索引号,例如我们取startIndex = 0,那么我只创建1分区1副本的topic,那么不就意味着所有的leader都会在startIndex的那台broker上?
所以这里的随机挑选也是保障服务端负载均衡的一部分,让分区尽可能的打散到各个broker中。

接着上面的图示和案例说,TopicA这个5分区3副本的Topic基于上面的算法得出了当前分区副本的分布情况,5个分区副本分布列表如下:

上面的这个分布列表其实就是我们常说的AR,即所有副本集合;而这其中还有一个概念,叫做优先副本。什么叫优先副本呢,我们以TopicA-0的AR列表(0,1,2)为例,kafka会优先选择broker-0来成为这个分区的leader副本,也就是按照AR列表的顺序号优先。最后我们TopicA的leader副本情况如下,5个分区的leader副本分散到5个不同的broker上,被尽可能的打散:

那么到这里,我们就回答了上面的两个问题:
1、创建topic的时候分区副本是如何分布的?随机挑选一个startIndex之后开始按照broker顺序遍历,每一轮结束后startIndex + 1
2、如何从这些副本中选出leader的?基于优先副本,也就是挑选出AR列表中的第一个brokerId上的副本成为leader。

最后我们来回答这个小节的问题,服务端负载均衡的负载均衡是如何保障的呢?
通过Topic创建时的算法,保障分区副本尽可能的打散到各个broker,然后通过优先副本选举选出AR中的第一个副本成为leader,让leader副本分散到各个broker中,以保障整体的负载均衡。

另外补充一下,随着时间的推移,集群状态的变化,或多或少会发生一些leader的切换和迁移,可能会导致某些broker上的leader会多一些从而导致负载不均衡,kafka针对这个问题提供了分区自动重平衡的功能,对应broker端的参数是 auto.leader.rebalance.enable,默认为true即开启。
开启之后呢,Controller会启动一个定时任务,每隔一段时间(5分钟)会去轮询所有的broker,计算每个broker节点的分区不平衡率,看是否超过了设置的阈值(默认10%)如果达到则自动进行分区迁移,将leader副本迁移回原先的优先副本所在的broker。
分区不平衡率 = 当前broker的非优先副本leader个数 / 当前broker的分区总数
需要注意的是,这个自动迁移的过程可能会引起负面的性能问题,从而影响到现有的业务,所以需要根据业务情况来判断是否要开启这个功能;另外kafka有提供专门的脚本去手动执行该迁移操作:kafka-preferred-replica-election.sh有兴趣的同学可以查一下具体的用法在此就不再赘述。


三、生产端是如何保障负载均衡的?

生产端保障负载均衡就很简单啦,就思考一下,生产消息的时候需要发往哪个leader副本呢?如何才能尽可能的保障每个副本发送的数据都差不多呢?我们来看下kafkaProducer的默认分区器实现源码:

    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
        int numPartitions = partitions.size();
        if (keyBytes == null) {
            int nextValue = nextValue(topic);
            List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
            if (availablePartitions.size() > 0) {
                int part = Utils.toPositive(nextValue) % availablePartitions.size();
                return availablePartitions.get(part).partition();
            } else {
                // no partitions are available, give a non-available partition
                return Utils.toPositive(nextValue) % numPartitions;
            }
        } else {
            // hash the keyBytes to choose a partition
            return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
        }
    }

    private int nextValue(String topic) {
        AtomicInteger counter = topicCounterMap.get(topic);
        if (null == counter) {
            counter = new AtomicInteger(ThreadLocalRandom.current().nextInt());
            AtomicInteger currentCounter = topicCounterMap.putIfAbsent(topic, counter);
            if (currentCounter != null) {
                counter = currentCounter;
            }
        }
        return counter.getAndIncrement();
    }

可以看到,当key不存在时,计算要发往哪个分区的算法,其实就是轮询算法,非常简单明了和有效

不过这里有一点值得提一下的就是,当key不存在时,会从当前存活的分区里面去轮询;而当key存在时,无论那个计算出来的分区存不存活都会往指定分区发送,这是一个比较重要的区别。


四、消费端是如何保障负载均衡的?

对于消费者而言,负载均衡的定义不是说如何均匀的拉取数据,而是总量数据一定的情况下,如何分发给组内的各个broker。
这里定义比较绕,举个栗子,现在Topic有100w数据,我消费组内有4个消费者;消费端的负载均衡就是想办法保障每个消费者能拉取到25w条数据。

明白定义之后,我们就来思考问题,消费端怎么才能做到负载均衡呢?
从前面我们知道,其实消费端消费的实体也是某个分区的leader副本,所以我们只需要把分区副本均匀的分配给当前组内的消费者是不是就可以保障负载大致是均衡的呢
是的,kafka的消费端程序,如果走消费组机制,就会提供一个分区分配的策略,对应的配置项为:partition.assignment.strategy默认值为RangeAssignor
每当rebalance的时候在SYNC阶段都会由consumer中的leader根据这个选出的策略进行分区分配计算,尽可能的让分区分配均匀,从而保障消费端的负载均衡。(对rebalance过程不熟悉的同学可以看下我之前的文章:十二

具体的官方提供策略有哪些呢?我们可以从源码中得知共三种:
具体这三种的分区分配算法过程我在这里就不写了,我们只需要明白有这几种策略可以去让消费者的分区分配尽可能的均衡,从而保障消费端的负载均衡
有兴趣的同学可以阅读朱忠华大佬的《深入理解Kafka》,这里面有详细的讲解。

另外就是,这个分区分配策略是可以自定义扩展的,继承抽象父类AbstractPartitionAssignor即可,然后重写指定函数即可,大家可以参照某一种官方实现去自定义实现。


五、什么情况下会出现负载不均衡?

最后,我们总结一下,什么情况下会出现负载不均衡?

服务端:
1、创建Topic时手动指定分区分配,可能导致leader分布不均。
2、集群状态变化导致leader迁移,kafka自带leader重平衡机制可自动迁移leader。
3、集群节点扩容,新增的节点如果不迁移分区或重建Topic不会有leader因此不会有流量打过去,从而导致负载不均衡,这也是kafka目前现存的比较大的痛点之一。
4、设置了broker.rack,分区副本算法会为了可用性尽可能避免副本创建在同一机架的机器上,从而可能导致负载不均衡。具体算法和上面算法相差不多,不过是broker列表会根据设置的机架信息先排序一次。

生产端:
1、指定业务key,会让对应key的消息都发送到指定分区,可能导致负载不均衡。
2、业务使用上,某些Topic的流量远大于其他Topic也可能导致负载不均衡。

消费端:
1、分区分配策略,有些策略可能会导致某些场景下分区分配出现不均匀的情况导致负载不均衡。
2、业务使用上,某些Topic的流量远大于其他Topic也可能导致负载不均衡。

如何解决负载不均衡呢?很可惜,计算机领域没有银弹,负载均衡问题也是一个非常大的命题,所以我们这一篇文章只能大概梳理一下kafka中的服务端/生产端/消费端是怎么尽可能保障负载均衡的,在我们了解了这些机制之后,再根据自己的业务场景去使用/运维kafka的时候才能尽可能的去避免出现负载不均衡的问题。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章