目录
主题的管理包括创建主题、查看主题信息、修改主题和删除主题等操作。可以通过Kafka提供的kafka-topics.sh 脚本来执行这些操作,这个脚本位于$KAFKA_HOME/bin/ 目录下,其核心代码仅有一行,具体如下:
exec $(dirname $0)/kafka-run - class.sh kafka.admin.Top ic Command "$@"
Topic相关
auto .create.topics .enable 设置为true会自动创建主题,和Es自动创建索引一样,这种情况默认不推荐使用。
kafka-topics.sh脚本中还提供了一个replica - assignment参数来手动指定分区副本的分配方案。
Kafka 从0.10.x 版本开始支持指定broker 的机架信息(机架的名称)。如果指定了机架信息,则在分区副本分配时会尽可能地让分区副本分配到不同的机架上。指定机架信息是通过broker 端参数broker.rack 来配置的,比如配置当前broker 所在的机架为“ RACKl”。
副本分片策略
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.MapInt, 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) {
//只有分区编号大于0且刚好分区编号已经轮流一遍broker时才递增下一个副本的间隔
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
为 -1 , 所 以 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)% brokerArray.Length=(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位置。
为什么不支持减少分区
按照Kafka现有的代码逻辑, 此功能完全可以实现,不过也会使代码的复杂度急剧增大。 实现此功能需要考虑的因素很多, 比如删除的分区中的消息该如何处理?如果随着分区一起消失则消息的可靠性得不到保障;如果需要保留则又需要考虑如何保留。直接存储到现有分区的尾部, 消息的时间戳就不会递增, 如此对于Spark、Flink这类需要消息时间戳(事件时间)的组件将会受到影响;如果分散插入现有的分区, 那么在消息量很大的时候, 内部的数据复制会占用很大的资源, 而且在复制期间, 此主题的可用性又如何得到保障?与此同时, 顺序性问题、 事务性问题, 以及分区和副本的状态机切换问题都是不得不面对的。反观这个功能的收益点却是很低的, 如果真的需要实现此类功能, 则完全可以重新创建一个分区数较小的主题, 然后将现有主题中的消息按照既定的逻辑复制过去即可。
主题端参数
分区的管理
优先副本
为了能够有效地治理负载失衡的情况,Kafka引入了优先副本(preferred replica)的概念。理想情况下, 优先副本就是该分区的leader副本, 所以也可以称之为preferred leader。Kafka要确保所有主题的优先副本在Kafka集群中均匀分布, 这样就保证了所有分区的leader均衡分布。如果leader 分布过于集中, 就会造成集群负载不均衡。
分区自动平衡
在Kafka中可以提供分区自动平衡的功能, 与此对应的broker端参数是auto.leader. rebalance.enable, 此参数的默认值为true, 即默认情况下此功能是开启的。如果开启分区 自动平衡的功能, 则Kafka的控制器会启动一个定时任务, 这个定时任务会轮询所有的broker 节点, 计算每个broker节点的分区不平衡率(broker中的不平衡率=非优先副本的leader个数/ 分区总数)是否超过leader.imbalance.per.broker.percentage参数配置的比值, 默认值为10%, 如果超过设定的比值则会自动执行优先副本的选举动作以求分区平衡。执行 周期由参数leader.imbalance.check.interval.seconds控制, 默认值为300秒, 即 5分钟。
复制限流
这里思考一下为什么需要复制限流,因为数据复制会占用额外的资源, 如果重分配的量太大必然会严重影响整体的性能, 尤其是处于业务高峰期的时候。减小重分配的粒度, 以小批次的方式来操作是一种可行的解决思路。如果集群中某个主题或某个分区的流量在某段时间内特别大, 那么只靠减小粒度是不足以应对的, 这时就需要有一个限流的机制, 可以对副本间的复制流量加以限制来保证重分配期间整体服务不会受太大的影响。副本间的复制限流有两种实现方式:kafka-config.sh脚本和kafka-reassign-partitions.sh 脚本 。
分区限制
默认分区数和文件描述符的大小有关,当硬限制数量爲4096的时候,会报Too many open files限制,可以使用ulimit -n 65535命令将上限提高到65535。