熟悉 Kafka的同學肯定知道,每個主題有多個分區,每個分區會存在多個副本,本文今天要討論的是這些副本是怎么樣放置在 Kafka集群的 Broker 中的。
大家可能在網上看過這方面的知識,網上對這方面的知識是千變一律,都是如下說明的:
為了更好的做負載均衡,Kafka盡量將所有的Partition均勻分配到整個集群上。Kafka分配Replica的算法如下:
-
將所有存活的N個Brokers和待分配的Partition排序
-
將第i個Partition分配到第(i mod n)個Broker上,這個Partition的第一個Replica存在於這個分配的Broker上,並且會作為partition的優先副本
-
將第i個Partition的第j個Replica分配到第((i + j) mod n)個Broker上
假設現在有5個 Broker,分區數為5,副本為3的主題,按照上面的說法,主題最終分配在整個集群的樣子如下:
但事實真的是這樣的嗎?實際上如果真按照這種算法,會存在以下明顯幾個問題:
-
所有主題的第一個分區都是存放在第一個Broker上,這樣會造成第一個Broker上的分區總數多於其他的Broker,這樣就失去了負載均衡的目的;
-
如果主題的分區數多於Broker的個數,多於的分區都是傾向於將分區發放置在前幾個Broker上,同樣導致負載不均衡。
所以其實上面的算法不准確。嚴格來說,上面的算法只是Kafka分配分區的一種特例(下面介紹算法部分會說明)。下面我們來看看 Kafka 內部到底是如何將分區分配到各個 Broker 中的,其具體算法實現函數就是 assignReplicasToBrokers,如下:
從上面的算法可以看出:
-
副本因子不能大於 Broker 的個數;
-
第一個分區(編號為0)的第一個副本放置位置是隨機從 brokerList 選擇的;
-
其他分區的第一個副本放置位置相對於第0個分區依次往后移。也就是如果我們有5個 Broker,5個分區,假設第一個分區放在第四個 Broker 上,那么第二個分區將會放在第五個 Broker 上;第三個分區將會放在第一個 Broker 上;第四個分區將會放在第二個 Broker 上,依次類推;
-
剩余的副本相對於第一個副本放置位置其實是由 nextReplicaShift 決定的,而這個數也是隨機產生的;
所以如果我們依次如下調用上面的程序,ret 變量的輸出結果會如下:
注意,你運行上面的程序結果可能和我的不一樣,因為上面算法中的 startIndex 和 nextReplicaShift 變量都是隨機生成的。其實 Kafka 創建主題就是這么調用算法的(fixedStartIndex
和 startPartitionId都是使用默認值)。另外,第一個放置的分區副本一般都是 Leader,其余的都是 Follow 副本,也就是說,上面輸出的List第一個元素就是 Leader 副本所在的 Broker 編號。
到這里我們應該知道,網上其他博客介紹的 Kafka 分區是如何分配到各個 Broker 上其實是將 startIndex 設置成 0, 同時 fixedStartIndex 設置成 1,這樣本文最開頭介紹的算法就對了。但其實 Kafka 內部並不是這樣調用的,大家注意。
如果我們還考慮機架的話,情況就更復雜了。這里為了簡便起見,我們假設startIndex = 4,fixedStartIndex = 1。現在如果我們有兩個機架的 Kafka 集群,brokers 0,
1 和 2 同屬於一個機架;brokers 3, 4 和 5 屬於另外一個機架。現在我們對這些 Broker 進行排序:0, 3, 1, 4, 2, 5(每個機架依次選擇一個Broker進行排序)。按照機架的 Kafka 分區放置算法,如果分區0的第一個副本放置到broker 4上面,那么其第二個副本將會放到broker 2上面,第三個副本將會放到 broker 5上面;同理,分區1的第一個副本放置到broker 2上面,其第二個副本將會放到broker 5上面,第三個副本將會放到 broker 0上面。這就保證了這兩個副本放置到不同的機架上面,即使其中一個機架出現了問題,我們的 Kafka 集群還是可以正常運行的。現在把機架因素考慮進去的話,我們的分區看起來像下面一樣:
從上圖可以看出,只要上面其中一個機架沒有問題,我們的數據仍然可以對外提供服務。這就大大提高了集群的可用性