kafka 重新分配节点_Kafka控制器-分区重分配

本文详细介绍了Kafka分区重分配的过程,包括触发条件、执行流程及分区状态变化。通过Kafka提供的脚本工具进行分区重分配,并解析了重分配过程中涉及的关键步骤,如新副本创建、ISR同步、旧副本下线等操作。
摘要由CSDN通过智能技术生成

分区重分配指的是将分区的副本重新分配到不同的代理节点上。如果ZK节点中分区的副本的新副本集合和当前分区副本集合相同,这个分区就不需要重新分配了。

分区重分配是通过监听ZK的 /admin/reassign_partitions 节点触发的,Kafka也提供了相应的脚本工具进行分区重分配,使用方法如下:

./kafka-reassign-partitions.sh --zookeeper XXX --reassignment-json-file XXX.json --execute

其中 XXX.json是分区重分配的JSON文件,格式如下:

{

"version":1,

"partitions":[

{"topic":"product", "partition":0, "replicas":[4,5,6]},

{"topic":"product", "partition":1, "replicas":[1,2,3]},

{"topic":"product", "partition":4, "replicas":[4,5,6]}

]}

假设主题 product 的分区数只有 {P0, P1},当执行上面的脚本时。此时会发现P4的分区对于 product 主题根本就不存在,此时就会忽略掉P4的副本迁移。对于P0和P1的副本重分配,可以简单的理解为下面的过程。

分区重分配命令接收

当使用脚本提交分区重分配时,接收命令的是 kafka.admin.ReassignPartitionsCommand#executeAssignment():

def executeAssignment(zkClient: KafkaZkClient, adminClientOpt: Option[JAdminClient], reassignmentJsonString: String, throttle: Throttle, timeoutMs: Long = 10000L) {

val (partitionAssignment, replicaAssignment) = parseAndValidate(zkClient, reassignmentJsonString)

val adminZkClient = new AdminZkClient(zkClient)

val reassignPartitionsCommand = new ReassignPartitionsCommand(zkClient, adminClientOpt, partitionAssignment.toMap, replicaAssignment, adminZkClient)

// 如果当前有正在执行中的分区重分配,则终止当前提交

if (zkClient.reassignPartitionsInProgress()) {

println("There is an existing assignment running.")

reassignPartitionsCommand.maybeLimit(throttle)

} else {

printCurrentAssignment(zkClient, partitionAssignment.map(_._1.topic))

if (throttle.interBrokerLimit >= 0 || throttle.replicaAlterLogDirsLimit >= 0)

println(String.format("Warning: You must run Verify periodically, until the reassignment completes, to ensure the throttle is removed. You can also alter the throttle by rerunning the Execute command passing a new value."))

/** 更新重分配数据至ZK */

if (reassignPartitionsCommand.reassignPartitions(throttle, timeoutMs)) {

println("Successfully started reassignment of partitions.")

} else

println("Failed to reassign partitions %s".format(partitionAssignment))

}

}

提交命令时,如果分区重分配还在进行,那么本次无法提交,意味着当前只能有一个执行的分区重分配。

重分配监听执行整体流程

当 /admin/reassign_partitions 被修改后,监听器会触发 PartitionReassignment 事件,其代码执行链如下所示:

下面我们看一下代码执行流程的展开。

分区重分配流程

控制器事件模型中 PartitionReassignment 事件,会触发调用processPartitionReassignment()。此时会注册监听ZK节点 /admin/reassign_partitions 变化,当重分配策略更新到ZK上时,该监听器就会被触发,然后执行分区重分配逻辑。

case PartitionReassignment =>

processPartitionReassignment()

private def processPartitionReassignment(): Unit = {

if (!isActive) return

/** 注册 /admin/reassign_partitions 节点变化监听 */

if (zkClient.registerZNodeChangeHandlerAndCheckExistence(partitionReassignmentHandler)) {

val partitionReassignment = zkClient.getPartitionReassignment

partitionReassignment.foreach { case (tp, newReplicas) =>

val reassignIsrChangeHandler = new PartitionReassignmentIsrChangeHandler(eventManager, tp)

/** 记录正在迁移的分区副本 */

controllerContext.partitionsBeingReassigned.put(tp, ReassignedPartitionsContext(newReplicas, reassignIsrChangeHandler))

}

maybeTriggerPartitionReassignment(partitionReassignment.keySet)

}

}

前置判断

对于是否需要分区重分配,在 maybeTriggerPartitionReassignment() 中做了一些判断取舍,其代码实现如下:

/**

* 如有下面情况发生,则不进行分区重分配:

* 1. Topic设置了删除标识;

* 2. 新副本与已经存在的副本相同;

* 3. 分区所有新分配的副本都不存活;

* 上面的情况发生时, 会输出一条日志, 并从ZK移除该分区副本的重分配记录

*/

private def maybeTriggerPartitionReassignment(topicPartitions: Set[TopicPartition]) {

val partitionsToBeRemovedFromReassignment = scala.collection.mutable.Set.empty[TopicPartition]

topicPartitions.foreach { tp =>

if (topicDeletionManager.isTopicQueuedUpForDeletion(tp.topic)) {

/** 如果topic已经设置了删除,不进行重分配(从需要副本迁移的集合中移除) */

partitionsToBeRemovedFromReassignment.add(tp)

} else {

val reassignedPartitionContext = controllerContext.partitionsBeingReassigned.get(tp).getOrElse {

throw new IllegalStateException(s"Initiating reassign replicas for partition $tp not present in " +

s"partitionsBeingReassigned: ${controllerContext.partitionsBeingReassigned.mkString(", ")}")

}

val newReplicas = reassignedPartitionContext.newReplicas

val topic = tp.topic

val assignedReplicas = controllerContext.partitionReplicaAssignment(tp)

if (assignedReplicas.nonEmpty) {

if (assignedReplicas == newReplicas) {

/** 新副本与已经存在的副本相同,不进行重分配 */

partitionsToBeRemovedFromReassignment.add(tp)

} else {

try {

/** 注册ISR变化监听 */

reassignedPartitionContext.registerReassignIsrChangeHandler(zkClient)

/** 设置正在迁移的副本不能删除 */

topicDeletionManager.markTopicIneligibleForDeletion(Set(topic), reason = "topic reassignment in progress")

/** 执行重分配 */

onPartitionReassignment(tp, reassignedPartitionContext)

} catch {

}

}

} else {

/** 分区副本都不存活,不进行重分配 */

partitionsToBeRemovedFromReassignment.add(tp)

}

}

}

removePartitionsFromReassignedPartitions(partitionsToBeRemovedFromReassignment)

}

对于前置校验的流程如下:

1、如果topic已经设置了删除,不进行重分配(从需要副本迁移的集合中移除);

2、如果分区副本都不存活,不进行重分配;

3、如果新副本与已经存在的副本相同,不进行重分配;

4、注册ISR变化监听;

5、设置将要迁移的副本为不能删除;

6、调用 onPartitionReassignment() 执行重分配。

执行分区重分配

分区重分配的执行是在 onPartitionReassignment() 中实现的,下面说明一下官方给出的几个技术名词:

RAR:新分配的副本列表;

OAR:原先的分区副本列表;

AR:当前副本列表,随着分配过程不断变化;

RAR-OAR:RAR与OAR的差集,即需要创建、数据迁移的新副本;

OAR-RAR:OAR与RAR的差集,即迁移后需要下线的副本。

重分配的具体代码实现如下所示:

/**

* 当需要进行分区重分配时, 会在[/admin/reassign_partitions]下创建一个节点来触发操作

* RAR: 重新分配的副本, OAR: 分区原副本列表, AR: 当前的分配的副本

*/

private def onPartitionReassignment(topicPartition: TopicPartition, reassignedPartitionContext: ReassignedPartitionsContext) {

val reassignedReplicas = reassignedPartitionContext.newReplicas

if (!areReplicasInIsr(topicPartition, reassignedReplicas)) {

/** 新分配的并没有全在ISR中 */

/** RAR-OAR */

val newReplicasNotInOldReplicaList = reassignedReplicas.toSet -- controllerContext.partitionReplicaAssignment(topicPartition).toSet

/** RAR+OAR */

val newAndOldReplicas = (reassignedPartitionContext.newReplicas ++ controllerContext.partitionReplicaAssignment(topicPartition)).toSet

/** 1.将AR更新为OAR + RAR */

updateAssignedReplicasForPartition(topicPartition, newAndOldReplicas.toSeq)

/** 2.向上面AR(OAR+RAR)中的所有副本发送LeaderAndIsr请求 */

updateLeaderEpochAndSendRequest(topicPartition, controllerContext.partitionReplicaAssignment(topicPartition), newAndOldReplicas.toSeq)

/** 3.新分配的副本状态更新为NewReplica(第2步中发送LeaderAndIsr时, 新副本会开始创建并且同步数据)*/

startNewReplicasForReassignedPartition(topicPartition, reassignedPartitionContext, newReplicasNotInOldReplicaList)

} else {

/** 4.等待所有的RAR都在ISR中 */

/** OAR - RAR */

val oldReplicas = controllerContext.partitionReplicaAssignment(topicPartition).toSet -- reassignedReplicas.toSet

/** 5.将副本状态设置为OnlineReplica */

reassignedReplicas.foreach { replica =>

replicaStateMachine.handleStateChanges(Seq(new PartitionAndReplica(topicPartition, replica)), OnlineReplica)

}

/** 6.将上下文中的AR设置为RAR */

/** 7.新加入的副本已经同步完成, LeaderAndIsr都更新到最新的结果 */

moveReassignedPartitionLeaderIfRequired(topicPartition, reassignedPartitionContext)

/** 8-9.将旧的副本下线 */

stopOldReplicasOfReassignedPartition(topicPartition, reassignedPartitionContext, oldReplicas)

/** 10.将ZK中的AR设置为RAR */

updateAssignedReplicasForPartition(topicPartition, reassignedReplicas)

/** 11.分区重分配完成, 从ZK /admin/reassign_partitions 节点删除迁移报文 */

removePartitionsFromReassignedPartitions(Set(topicPartition))

/** 12.发送metadata更新请求给所有存活的broker */

sendUpdateMetadataRequest(controllerContext.liveOrShuttingDownBrokerIds.toSeq, Set(topicPartition))

/** 如果topic标记了删除, 此时唤醒删除线程*/

topicDeletionManager.resumeDeletionForTopics(Set(topicPartition.topic))

}

}

上面代码执行的过程描述如下:

1.将AR更新为OAR+RAR;

2.向上面AR(OAR+RAR)中的所有副本发送LeaderAndIsr请求;

3.新分配的副本状态更新为NewReplica(第2步中发送LeaderAndIsr时, 新副本会开始创建并且同步数据);

4.等待所有的RAR都在ISR中;

5.将副本状态设置为OnlineReplica;

6.将上下文中的AR设置为RAR;

7.新加入的副本已经同步完成, LeaderAndIsr都更新到最新的结果;

8-9.将旧的副本下线;

10.将ZK中的AR设置为RAR;

11.分区重分配完成, 从ZK /admin/reassign_partitions 节点删除迁移报文;

12.发送metadata更新请求给所有存活的broker;

重分配简单描述

通过代码层面看起来不是很好理解,下面简单描述一下执行过程:

1、创建新的副本,开始同步数据,等所有新副本都加入了ISR后,在RAR中进行Leader选举;

2、下线不需要的副本(OAR-RAR),下线完成后将AR(即RAR)信息更新到ZK中;

3、发送LeaderAndIsr给存活broker。

假如初始情况下,分区副本在 {1,2,3} 三个 Broker 上;重分配之后在{4,5,6}上,此时变化过程如下图所示:

参考:《Kafka技术内幕》、《Apache Kafka 源码剖析》、Kafka源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值