本文主要討論kafka 分區的初始分配、重分配(本文不討論consumer情況)
首先,從kafka的底層實現來說,主題與分區都是邏輯上的概念,分區可以有一至多個副本,每個副本對於一個日誌文件(物理層面)。
要研究kafka分區的初始分配、重分配需知曉以下概念。
1、AR(Assigned Replicas):分區中的所有副本
2、ISR(In-Sync Replicas)與leader副本保持一定程度同步的副本(包括leader副本)
3、OSR(Out-of-Sync Replicas)與leade副本同步滯後過多的副本。
由此可見AR=ISR+OSR,一般情況下AR=ISR,OSR爲空,kafka集羣的一個broker中最多隻能有一個它的副本,我們可以將leader副本所在的broker節點叫做分區的leader節點,而follower副本所在的broker節點叫做分區的follower節點。
接下來開始本文的重點討論
一、kafka 分區的初始分配
這裏的分區分配☞爲集羣創建指定主題時的分區副本分配方案,即在哪個broker中創建哪些分區的副本。
在創建主題時,如果使用了replica-assignment參數,那麼就按照指定的方案來進行分區副本的創建;如果沒有則按照kakfa內部實現計算分配。內部分配分2種策略:1、未指定機架 2、指定機架 ,如果集羣中的所有broker節點都沒有配置broker.rack參數,則採用disable-rack-aware參數來創建主題,否則採用指定機架創建主題。
1、未指定機架策略:均勻分配所有副本到所有的broker上
private def assignReplicasToBrokersRackUnaware(
nPartitions: Int, //分區數
replicationFactor: Int, //副本因子
brokerList: Seq[Int], //集羣中的broker列表
fixedStartIndex: Int , //起始索引,即第一個副本分配的位置,默認值爲-1
startPatitionId: Int //起始分區編號,默認值爲-1
):
Map[Int,Seq[Int]] = {
val ret = mutable.Map[Int,Seq[Int]]() // 保存分配結果的集合
val brokerArray = brokerList.toArray //brokerId的列表
//如果起始索引fixedStartIndex小於0,則根據broker列表的長度隨機生成一個,以此來保證是有效的brokerId
val startIndex = if(fixedStartIndex>0) fixedStartIndex
else rand.nextInt(brokerArray.length)
//確保起始分區號不小於0
var currentPartitionId = math.max(0,startPatitionId)
//指定了副本的間隔,目的是爲了更均勻的將副本分配到不同的broker上
var nextReplicaShift = if(fixedStartIndex>=0) fixedStartIndex
else rand.nextInt(brokerArray.length)
//輪詢所有分區,將每個分區的副本分配到不同的broker上
for(_<- 0 until nPartitions){
if(currentPartitionId>0 && currentPartitionId%brokerArray.length == 0)
nextReplicaShift+=1
//從broker-list中選定一個隨機的位置,從這個位置開始,將每一個partition的第一個replica依次賦予brokerList中的broker.
val firstReplicaIndex = (currentPartitionId+startIndex)%brokerArray.length
val replicaBuffer = mutable.ArrayBuffer(brokerArray(firstReplicaIndex))
//保存該分區所有副本分配的broker集合
for(j<- 0 until replicationFactor - 1)
replicaBuffer+=brokerArray(replicaIndex(firstReplicaIndex,nextReplicaShift,j,brokerArray.length))//爲其餘的副本分配broker
//保存該分區的所有副本的分配信息
ret.put(currentPartitionId,replicaBuffer)
//繼續爲下一個分區分配副本
currentPartitionId+=1
}
ret
}
//1,0,1,3 當分配好了第一個replica之後,剩下的replica以第一個replica所在的broker爲基準,依次賦予之後的broker
private def replicaIndex(firstReplicaIndex: Int, secondReplicaShift: Int, replicaIndex: Int, nBrokers: Int): Int = {
val shift = 1 + (secondReplicaShift + replicaIndex) % (nBrokers - 1)
(firstReplicaIndex + shift) % nBrokers
}
2、指定機架分配分區
如機架與broker節點的對照關係如下:
rack1:0,1,2
rack2:3,4,5
rack3:6,7,8
未指定機架的brokeArray爲[0,1,2,3,4,5,6,7,8],而指定機架的brokerArray爲[0,3,6,1,4,7,2,5,8],循環爲每個分區分配副本,儘量保證分區在機架中均勻分佈,這裏不是簡單的將這個broker添加到當前分區的副本列表中,還要經過一層篩選,滿足以下任意一個條件的broker不能被添加到當前分區的副本列表中:1、如果此broker所在的機架中已經存在一個broker擁有該分區的副本,並且還有其他機架中沒有任何一個broker擁有該分區的副本 2、如果此broker中已經擁有了該分區的副本,並且還有其他broker中沒有該分區的副本
二、kafka分區的重分配
1、優先副本的選取
分區使用多副本機制來提升可靠性,leader副本負責讀寫,follower只負責在內部進行消息的同步,一旦leader節點掛掉,kafka需要從follower副本中挑選一個作爲leader副本。如:原先3個broker,3個分區,當其中一個broker掛掉之後,剩下的2個broker承擔的分區數變成2、1,如此一來,均衡的負載變得不均衡,爲了有效的治理負載均衡問題,kakfa引入了優先副本的概念,所謂的優先副本是指AR集合列表的第一個副本,理想情況下,優先副本就是該分區的leader副本。kafka要確保所有的主題的優先副本在kafka中均勻分佈,這樣就保證了整個kafka集羣的負載均衡。
於此:有個參數需要了解:
auto.leader.rebalance.enable:此參數的默認值爲true,如果一個分區有3個副本,且3個副本的優先級爲0,1,2,根據副本優先概念,0會作爲leader,當0掛掉後,會啓動1作爲leader,當0恢復後,會重新啓用0作爲leader,這就是分區自動平衡,保證負載均衡,執行週期由參數leader.imbalance.check.interval.seconds控制,默認5分鐘,分區自動平衡會造成客戶端的堵塞,生產環境不建議開啓,可手動執行分區平衡。
2、分區重分配
當一個節點的分區副本不可用時,kafka不會自動將這些失效的分區副本遷移到集羣剩餘的可用的broker節點上,會影響負載均衡以及服務的可用性、可靠性,當集羣中新增一個節點時,只有新創建的主題分區纔有可能分配到這個節點上,之前的主題分區不會自動分配到這個新的節點上,也會影響其負載均衡
爲了解決上訴問題,引入分區重分配方案,kafka提供了kafka-reassign-partitions.sh腳本來執行分區重分配的工作(集羣擴容、節點失效場具下工作) 主要分爲以下3個步驟:首先創建一個包含主題清單的JSON文件,其次根據主題清單和broker節點清單生成一份重分配方案,最後根據這份方案執行具體的重分配動作。
具體操作,後續本地運行一遍後補上
參考:《深入理解kafka核心設計與實踐原理》