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的時候才能儘可能的去避免出現負載不均衡的問題。


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