主要介绍是未指定机架信息的分配策略,kafka版本是2.0.0,具体实现为kafka.admin.AdminUtils.scala文件中的
assignReplicasToBrokersRackUnaware()方法,该方法的内容如下:
private def assignReplicasToBrokersRackUnaware(nPartitions: Int,//分区数
replicationFactor: Int,//副本因子
brokerList: Seq[Int],//集群中broker列表
fixedStartIndex: Int,//起始索引,即第一个副本分配的位置,默认值为-1
startPartitionId: Int): Map[Int, Seq[Int]] = {//起始分区编号,默认值为-1
// fixedStartIndex表示第一个副本分配的位置,默认为-1
// startPartitionId表示起始分区编号,默认为-1
// ret表示<partition,Seq[replica所在brokerId]>的关系
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, startPartitionId)
// 指定了副本的间隔,目的是为了更均匀地将副本分配到不同的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
val firstReplicaIndex = (currentPartitionId + startIndex) % brokerArray.length
val replicaBuffer = mutable.ArrayBuffer(brokerArray(firstReplicaIndex))
// 保存该分区所有副本分配的broker集合
for (j <- 0 until replicationFactor - 1)
// 为其余的副本分配broker
replicaBuffer += brokerArray(replicaIndex(firstReplicaIndex, nextReplicaShift, j, brokerArray.length))
// 保存该分区所有副本的分配信息
ret.put(currentPartitionId, replicaBuffer)
// 继续为下一个分区分配副本
currentPartitionId += 1
}
ret
}
该方法参数列表中的 fixedStartlndex 和 startPartitionld 值是从上游的方法 中调用传下来的,都是一 1 ,分别表示第一个副本分配的位置和起始分区编号。 assignReplicasToBrokersRackUnaware()方法的核心是遍历 每 个分区 partition , 然后从 brokerArray ( brokerld 的列 表)中 选取replicationFactor 个 brokerld 分配给这个 partition 。该方法首先创建一个可变的 Map 用来存放该方法将要返回的结果 ,即分区 partition 和分配副本的映射关系 。 由于 fixedStartlndex 为 1 ,所以 startlndex 是一个随机数,用来计算一个起始分配的 brokerId,同时又因为 startPartitionld 为一l , 所 以 currentPartitionld 的为 0,可见默认情况下创建主题时总是从编号为 0 的分区依次轮询进行分配 。nextReplicaShift 表示下一次副本分配相对于前一次分配的位移量 ,从字面上理解有 点绕 口 。举个例子 : 假设集群中有 3 个 broker 节点 , 对应于代码 中的 brokerArray,创建的某个主题中有3 个副本和 6 个分区,那么 首先从 partitionld ( partition 的编号) 为 0 的 分区开始进行分配,假设第一次计算(由 rand .nextlnt(brokerArray.length)随机产生)得到的nextReplicaShift的值为1,第一次随机产生的 startlndex 值为 2,那么 partitionld 为 0 的第一个副本的位置 ( 这里指的是brokerArray 的数组下标 ) firstReplicalndex = (currentPartitionld + startlndex) % brokerArra川ength=(0+2)%3=2 ,第二个副本的位置为 replicalndex(firstReplicalndex, nextReplicaShift, j , brokerArray.length)= replicalndex(2, 1, 0, 3 )=?, 这里引 入了一个新 的方法 replicalndex() , 不过这个方法很简单, 具体如下:
private def replicaIndex(firstReplicaIndex : Int , secondReplicaShift : Int ,
replicaIndex : Int , nBrokers : Int) : Int = {
val shift = 1 + (secondReplicaShift + replicaIndex ) % ( nBrokers - 1 )
(firstReplicaIndex + shift) % nBrokers
}
说明:该方法是基于第一个副本分配的broker位置,再根据偏移量计算出后续副本被分配到的broker位置。
在计算副本分配位置的时候,第一个副本的位置已经在循环外面计算过了,并且放入数组中了。后续计算剩余副本的
过程只计算了副本数-1次。在副本为3的情况下,replicaIndex的值只能是0和1。secondReplicaShift的值在分区不变的情况是不会变化的,一直是1。那么计算偏移量公式(secondReplicaShift + replicaIndex ) % ( nBrokers - 1 )可以理解为:分区副本循环一轮broker的偏移量由secondReplicaShift控制,同一分区的副本偏移量由replicaIndex控制。对broker数量-1(3-1)取余只能在0和1中,再加1的话,偏移量就只能是1和2,这样副本的偏移量不会等于0,也就分配的均匀了。
继续计算replicaIndex(2, 1, 0, 3) = (2+(1+(1+0)%(3-1)))%3 = 1。继续计算下一个副本的位置replicaIndex(2, 1, 1, 3)=(2+(1+(1+1)%(3-1)))%3=0。所以partitionId为0的副本分配位置列表[2,1,0]。
给出最后的分配结果,buffer中的第一个为Leader副本。
分区0 ArrayBuffer(2, 1, 0)
分区1 ArrayBuffer(0, 2, 1)
分区2 ArrayBuffer(1, 0, 2)
分区3 ArrayBuffer(2, 0, 1)
分区4 ArrayBuffer(0, 1, 2)
分区5 ArrayBuffer(1, 2, 0)
参考:《深入理解kafka:核心设计与实践原理》