defhandleDeleteTopicsRequest(request: RequestChannel.Request): Unit = { ...... val deleteTopicRequest = request.body[DeleteTopicsRequest] val results = newDeletableTopicResultCollection(deleteTopicRequest.data.topicNames.size) val toDelete = mutable.Set[String]()
if (topicsToBeDeleted.nonEmpty) { info(s"Starting topic deletion for topics ${topicsToBeDeleted.mkString(",")}")
// mark topic ineligible for deletion if other state changes are in progress // 如果此时 topic 有分区迁移的任务尚未完成,则标记为不满足删除条件(ineligible for deletion) // 并记录到 ctx 中,后续删除的时候会跳过 topicsToBeDeleted.foreach { topic => val partitionReassignmentInProgress = controllerContext.partitionsBeingReassigned.map(_.topic).contains(topic) if (partitionReassignmentInProgress) topicDeletionManager.markTopicIneligibleForDeletion(Set(topic), reason = "topic reassignment in progress") }
// if you come here, then no replica is in TopicDeletionStarted and all replicas are not in // TopicDeletionSuccessful. That means, that either given topic haven't initiated deletion // or there is at least one failed replica (which means topic deletion should be retried). // // 到这里, // 所有的 replica 都不在 ReplicaDeletionStarted 状态, // 并且存在 replica 不在 ReplicaDeletionSuccessful 状态, // 有两种可能, // 一是这个 topic 可能还没有初始化删除任务, // 二是有部分 replica 删除失败了,处于 Ineligible to delete 状态, // 这时候就需要重试删除任务 if (controllerContext.isAnyReplicaInState(topic, ReplicaDeletionIneligible)) { topicsEligibleForRetry += topic } }
// Add topic to the eligible set if it is eligible for deletion. // // 下列条件都满足时,可以删除 topic // 1. topicsToBeDeleted 中包含 topic // 2. replica 都不处于 ReplicaDeletionStarted 状态(started 表示已经处于删除进程中了) // 3. topicsIneligibleForDeletion 中不包含 topic if (isTopicEligibleForDeletion(topic)) { info(s"Deletion of topic $topic (re)started") topicsEligibleForDeletion += topic } }
// check if leader and isr path exists for partition. If not, then it is in NEW state // // Controller 为 partition 选出了 leader 之后,会先将 LeaderAndIsr 信息保存在 // zk 的 /brokers/topics/[topic]/partitions/[partition]/state 路径上 // 再将 LeaderAndIsr 信息保存到 ctx 的 partitionLeadershipInfo 中 // // 这里去 ctx 的 partitionLeadershipInfo 中查找 partition 的 leader // 如果没找到,说明这个 partition 处于 NewPartition 状态 controllerContext.partitionLeadershipInfo(topicPartition) match {
caseSome(currentLeaderIsrAndEpoch) =>
// else, check if the leader for partition is alive. If yes, it is in Online state, else it is in Offline state // 如果 broker 存活,则将 partition 置为 OnlinePartition 状态,否则置为 OfflinePartition if (controllerContext.isReplicaOnline(currentLeaderIsrAndEpoch.leaderAndIsr.leader, topicPartition)) controllerContext.putPartitionState(topicPartition, OnlinePartition) else controllerContext.putPartitionState(topicPartition, OfflinePartition) caseNone => controllerContext.putPartitionState(topicPartition, NewPartition) } } }
deftriggerOnlinePartitionStateChange(): Unit = { val partitions = controllerContext.partitionsInStates(Set(OfflinePartition, NewPartition)) triggerOnlineStateChangeForPartitions(partitions) }
privatedeftriggerOnlineStateChangeForPartitions(partitions: collection.Set[TopicPartition]): Unit = { // try to move all partitions in NewPartition or OfflinePartition state to OnlinePartition state except partitions // that belong to topics to be deleted // // 除了待删除 topic 的 partition,将其他所有处于 NewPartition 或 OfflinePartition // 状态的 partition 置为 OnlinePartition 状态 val partitionsToTrigger = partitions.filter { partition => !controllerContext.isTopicQueuedUpForDeletion(partition.topic) }.toSeq
result } catch { case e: ControllerMovedException => // 不再是 controller 了,无权更新状态机 error(s"Controller moved to another broker when moving some partitions to $targetState state", e) throw e case e: Throwable => ...... } } else { Map.empty } }
if (uninitializedPartitions.nonEmpty) { // 新创建的分区,在 zk 的 /brokers/topics/<topic>/partitions/<partition> // 目录下写入 leader 和 isr 信息 val successfulInitializations = initializeLeaderAndIsrForPartitions(uninitializedPartitions) successfulInitializations.foreach { partition => stateChangeLog.info(s"Changed partition $partition from ${partitionState(partition)} to $targetState with state " + s"${controllerContext.partitionLeadershipInfo(partition).get.leaderAndIsr}") // 写入成功,更新状态为 OnlinePartition controllerContext.putPartitionState(partition, OnlinePartition) } }
// 从上文 partitionsToElectLeader 的来源可知,这一步有两种可能: // 1. 从 OfflinePartition 到 OnlinePartition // 2. 从 OnlinePartition 到 OnlinePartition if (partitionsToElectLeader.nonEmpty) { val electionResults = electLeaderForPartitions( partitionsToElectLeader, partitionLeaderElectionStrategyOpt.getOrElse( thrownewIllegalArgumentException("Election strategy is a required field when the target state is OnlinePartition") ) )
// 选举成功,更新 ctx 信息,记录日志 electionResults.foreach { case (partition, Right(leaderAndIsr)) => stateChangeLog.info( s"Changed partition $partition from ${partitionState(partition)} to $targetState with state $leaderAndIsr" ) controllerContext.putPartitionState(partition, OnlinePartition) case (_, Left(_)) => // Ignore; no need to update partition state on election error }
electionResults } else { Map.empty } caseOfflinePartition => // 直接更新 ctx validPartitions.foreach { partition => if (traceEnabled) stateChangeLog.trace(s"Changed partition $partition state from ${partitionState(partition)} to $targetState") controllerContext.putPartitionState(partition, OfflinePartition) } Map.empty caseNonExistentPartition => // 直接更新 ctx validPartitions.foreach { partition => if (traceEnabled) stateChangeLog.trace(s"Changed partition $partition state from ${partitionState(partition)} to $targetState") controllerContext.putPartitionState(partition, NonExistentPartition) } Map.empty } }
// 找到存活的 replica val liveReplicasPerPartition = replicasPerPartition.map { case (partition, replicas) => val liveReplicasForPartition = replicas.filter(replica => controllerContext.isReplicaOnline(replica, partition)) partition -> liveReplicasForPartition } val (partitionsWithoutLiveReplicas, partitionsWithLiveReplicas) = liveReplicasPerPartition.partition { case (_, liveReplicas) => liveReplicas.isEmpty }
// 对于 replica 全挂的分区,记录日志 partitionsWithoutLiveReplicas.foreach { case (partition, replicas) => val failMsg = s"Controller $controllerId epoch ${controllerContext.epoch} encountered error during state change of " + s"partition $partition from New to Online, assigned replicas are " + s"[${replicas.mkString(",")}], live brokers are [${controllerContext.liveBrokerIds}]. No assigned " + "replica is alive." logFailedStateChange(partition, NewPartition, OnlinePartition, newStateChangeFailedException(failMsg)) }
// 对于有 replica 存活的分区 val leaderIsrAndControllerEpochs = partitionsWithLiveReplicas.map { case (partition, liveReplicas) =>
// 将 liveReplicas 中第一个设置为 leader,其他的设置为 isr val leaderAndIsr = LeaderAndIsr(liveReplicas.head, liveReplicas.toList) val leaderIsrAndControllerEpoch = LeaderIsrAndControllerEpoch(leaderAndIsr, controllerContext.epoch) partition -> leaderIsrAndControllerEpoch }.toMap
val createResponses = try { // 在 zk 上创建目录 /brokers/topics/<topic>/partitions // 在 zk 上创建节点 /brokers/topics/<topic>/partitions/<partition> zkClient.createTopicPartitionStatesRaw(leaderIsrAndControllerEpochs, controllerContext.epochZkVersion) } catch { case e: ControllerMovedException => error("Controller moved to another broker when trying to create the topic partition state znode", e) throw e case e: Exception => partitionsWithLiveReplicas.foreach { case (partition, _) => logFailedStateChange(partition, partitionState(partition), NewPartition, e) } Seq.empty }
/** * Repeatedly attempt to elect leaders for multiple partitions until there are no more remaining partitions to retry. * * 不断尝试为传入的多个分区选举,直到所有分区都成功选出 leader * * @param partitions The partitions that we're trying to elect leaders for. * @param partitionLeaderElectionStrategy The election strategy to use. * @return A map of failed and successful elections. The keys are the topic partitions and the corresponding values are * either the exception that was thrown or new leader & ISR. */ privatedefelectLeaderForPartitions( partitions: Seq[TopicPartition], partitionLeaderElectionStrategy: PartitionLeaderElectionStrategy ): Map[TopicPartition, Either[Throwable, LeaderAndIsr]] = { var remaining = partitions val finishedElections = mutable.Map.empty[TopicPartition, Either[Throwable, LeaderAndIsr]]
while (remaining.nonEmpty) { val (finished, updatesToRetry) = doElectLeaderForPartitions(remaining, partitionLeaderElectionStrategy)
// 从 zk 的 /brokers/topics/<topic>/partitions/<partition>/state // 节点查询分区信息 val getDataResponses = try { zkClient.getTopicPartitionStatesRaw(partitions) } catch { case e: Exception => return (partitions.iterator.map(_ -> Left(e)).toMap, Seq.empty) } val failedElections = mutable.Map.empty[TopicPartition, Either[Exception, LeaderAndIsr]] val validLeaderAndIsrs = mutable.Buffer.empty[(TopicPartition, LeaderAndIsr)]
getDataResponses.foreach { getDataResponse => val partition = getDataResponse.ctx.get.asInstanceOf[TopicPartition]
// 查询 ctx 中当前的分区状态 val currState = partitionState(partition)
if (getDataResponse.resultCode == Code.OK) { TopicPartitionStateZNode.decode(getDataResponse.data, getDataResponse.stat) match { caseSome(leaderIsrAndControllerEpoch) => if (leaderIsrAndControllerEpoch.controllerEpoch > controllerContext.epoch) { // 说明当前的 controller 已经被夺权了 val failMsg = s"Aborted leader election for partition $partition since the LeaderAndIsr path was " + s"already written by another controller. This probably means that the current controller $controllerId went through " + s"a soft failure and another controller was elected with epoch ${leaderIsrAndControllerEpoch.controllerEpoch}." failedElections.put(partition, Left(newStateChangeFailedException(failMsg))) } else { // 将 zk 上真实有效的 leaderAndIsr 信息存起来,以备后续使用 validLeaderAndIsrs += partition -> leaderIsrAndControllerEpoch.leaderAndIsr }
caseNone => val exception = newStateChangeFailedException(s"LeaderAndIsr information doesn't exist for partition $partition in $currState state") failedElections.put(partition, Left(exception)) }
} elseif (getDataResponse.resultCode == Code.NONODE) { val exception = newStateChangeFailedException(s"LeaderAndIsr information doesn't exist for partition $partition in $currState state") failedElections.put(partition, Left(exception)) } else { failedElections.put(partition, Left(getDataResponse.resultException.get)) } }
// zk 上找不到对应的节点,直接退出 if (validLeaderAndIsrs.isEmpty) { return (failedElections.toMap, Seq.empty) }
// 选举 val (partitionsWithoutLeaders, partitionsWithLeaders) = partitionLeaderElectionStrategy match {
if (isDebugEnabled) { updatesToRetry.foreach { partition => debug(s"Controller failed to elect leader for partition $partition. " + s"Attempted to write state ${adjustedLeaderAndIsrs(partition)}, but failed with bad ZK version. This will be retried.") } }
/* For the provided set of topic partition and partition sync state it attempts to determine if unclean * leader election should be performed. Unclean election should be performed if there are no live * replica which are in sync and unclean leader election is allowed (allowUnclean parameter is true or * the topic has been configured to allow unclean election). * * 根据传入的 partition 及其 leaderAndIsr,判断是否允许 unclean leader election。 * * unclean leader election 是指在 isr 列表为空的情况下, Kafka 选择一个非 isr 副本作为新的 Leader, * 此时存在丢失数据的风险,需要配置文件中的 unclean.leader.election.enable 为 true * 或者 topic 被配置为允许 unclean election 时才会执行。 * * @param leaderIsrAndControllerEpochs set of partition to determine if unclean leader election should be * allowed * @param allowUnclean whether to allow unclean election without having to read the topic configuration * @return a sequence of three element tuple: * 1. topic partition * 2. leader, isr and controller epoc. Some means election should be performed * 3. allow unclean */ privatedefcollectUncleanLeaderElectionState( leaderAndIsrs: Seq[(TopicPartition, LeaderAndIsr)], allowUnclean: Boolean ): Seq[(TopicPartition, Option[LeaderAndIsr], Boolean)] = {
// 根据 isr 中是否有 online replica 分为两组 val (partitionsWithNoLiveInSyncReplicas, partitionsWithLiveInSyncReplicas) = leaderAndIsrs.partition { case (partition, leaderAndIsr) => val liveInSyncReplicas = leaderAndIsr.isr.filter(controllerContext.isReplicaOnline(_, partition)) liveInSyncReplicas.isEmpty }
// adding of unseenTopicsForDeletion to topics with deletion started must be done after the partition // state changes to make sure the offlinePartitionCount metric is properly updated // // 一定要先更新 partition 的状态 // 再去把还没开始删除的 topic 列表加入到 topicsWithDeletionStarted 中; // 否则会影响到 offlinePartitionCount 这个 metric 的值 // // 将 topic 加入到 topicsWithDeletionStarted 列表中 controllerContext.beginTopicDeletion(unseenTopicsForDeletion) }
// send update metadata so that brokers stop serving data for topics to be deleted // 发送更新元数据的请求 client.sendMetadataUpdate(topics.flatMap(controllerContext.partitionsForTopic))
} catch { case e: ControllerMovedException => error(s"Controller moved to another broker when moving some replicas to $targetState state", e) throw e case e: Throwable => error(s"Error while moving some replicas to $targetState state", e) } } } ...... }
// 如果该副本是 Leader if (leaderIsrAndControllerEpoch.leaderAndIsr.leader == replicaId) { // 记录错误日志。Leader副本不能被设置成 NewReplica 状态 val exception = newStateChangeFailedException(s"Replica $replicaId for partition $partition cannot be moved to NewReplica state as it is being requested to become leader") logFailedStateChange(replica, currentState, OfflineReplica, exception) } else { // 如果该副本不是 Leader, // 给该副本所在的 Broker 发送 LeaderAndIsrRequest // 然后给集群当前所有 Broker 发送 UpdateMetadataRequest 通知它们该分区数据发生变更 controllerBrokerRequestBatch.addLeaderAndIsrRequestForBrokers(Seq(replicaId), replica.topicPartition, leaderIsrAndControllerEpoch, controllerContext.partitionFullReplicaAssignment(replica.topicPartition), isNew = true) if (traceEnabled) logSuccessfulTransition(stateLogger, replicaId, partition, currentState, NewReplica)
currentState match { caseNewReplica => val assignment = controllerContext.partitionFullReplicaAssignment(partition)
// 如果 replica 不在 assignment cache 中,则添加到 assignment cache 中 if (!assignment.replicas.contains(replicaId)) { error(s"Adding replica ($replicaId) that is not part of the assignment $assignment") val newAssignment = assignment.copy(replicas = assignment.replicas :+ replicaId) controllerContext.updatePartitionFullReplicaAssignment(partition, newAssignment) }
updatedLeaderIsrAndControllerEpochs.forKeyValue { (partition, leaderIsrAndControllerEpoch) => stateLogger.info(s"Partition $partition state changed to $leaderIsrAndControllerEpoch after removing replica $replicaId from the ISR as part of transition to $OfflineReplica")
/** * Try to remove a replica from the isr of multiple partitions. * Removing a replica from isr updates partition state in zookeeper. * * 尝试从多个分区的 ISR(同步副本集合)中移除一个副本。 * 从 ISR 中移除副本会更新 Zookeeper 中的分区状态。 * * @param replicaId The replica being removed from isr of multiple partitions * 要从多个分区的 ISR 中移除的副本 ID * * @param partitions The partitions from which we're trying to remove the replica from isr * 需要从这些分区的 ISR 中移除副本 * *@return A tuple of two elements: * 1. The updated Right[LeaderIsrAndControllerEpochs] of all partitions for which we successfully * removed the replica from isr. Or Left[Exception] corresponding to failed removals that should * not be retried * 2. The partitions that we should retry due to a zookeeper BADVERSION conflict. Version conflicts can occur if * the partition leader updated partition state while the controller attempted to update partition state. * *一个包含两个元素的元组: * 1. 一个映射,包含成功从 ISR 中移除副本的所有分区的新状态: * - 如果成功,返回 Right[LeaderIsrAndControllerEpochs]。 * - 如果失败且不应重试,返回 Left[Exception]。 * 2. 由于 Zookeeper 的 BADVERSION 冲突而需要重试的分区列表。 * 版本冲突可能发生在 controller 尝试更新分区状态的同时,分区的 Leader 也更新了分区状态。 */ privatedefdoRemoveReplicasFromIsr( replicaId: Int, partitions: Seq[TopicPartition] ): (Map[TopicPartition, Either[Exception, LeaderIsrAndControllerEpoch]], Seq[TopicPartition]) = {
// zk 上找不到 node 并且 partition 不在删除队列中,找出这部分数据 val exceptionsForPartitionsWithNoLeaderAndIsrInZk: Map[TopicPartition, Either[Exception, LeaderIsrAndControllerEpoch]] = partitionsWithNoLeaderAndIsrInZk.iterator.flatMap { partition => if (!controllerContext.isTopicQueuedUpForDeletion(partition.topic)) { val exception = newStateChangeFailedException( s"Failed to change state of replica $replicaId for partition $partition since the leader and isr " + "path in zookeeper is empty" ) Option(partition -> Left(exception)) } elseNone }.toMap
if (isDebugEnabled) { updatesToRetry.foreach { partition => debug(s"Controller failed to remove replica $replicaId from ISR of partition $partition. " + s"Attempted to write state ${adjustedLeaderAndIsrs(partition)}, but failed with bad ZK version. This will be retried.") } }
/** * Invoked by onTopicDeletion with the list of partitions for topics to be deleted * It does the following - * 1. Move all dead replicas directly to ReplicaDeletionIneligible state. Also mark the respective topics ineligible * for deletion if some replicas are dead since it won't complete successfully anyway * 2. Move all replicas for the partitions to OfflineReplica state. This will send StopReplicaRequest to the replicas * and LeaderAndIsrRequest to the leader with the shrunk ISR. When the leader replica itself is moved to OfflineReplica state, * it will skip sending the LeaderAndIsrRequest since the leader will be updated to -1 * 3. Move all replicas to ReplicaDeletionStarted state. This will send StopReplicaRequest with deletePartition=true. And * will delete all persistent data from all replicas of the respective partitions * * 1. 把宕机的 replica 设置为 ReplicaDeletionIneligible 状态。 * 其他相关的 topics 也都设置为该状态。因为 broker 挂了,这个请求无法完成。 * 2. 将分区的所有 replica 设置为 OfflineReplica。 * 向其他 replica 发送 StopReplicaRequest 请求 * 并向 leader 副本发送 LeaderAndIsrRequest 请求,参数中携带了发生减员的 ISR。 * 当 leader 副本被设置为 OfflineReplica 时,不会发送 LeaderAndIsrRequest, * 因为 leader 已经不存在了(被更新为-1)。 * 3. 将所有的 replica 加入 ReplicaDeletionStarted 列表。 * 发送 StopReplicaRequest(deletePartition=true) 给 replica, * 然后从磁盘中删除数据 * */ private def onPartitionDeletion(topicsToBeDeleted: Set[String]): Unit = { val allDeadReplicas = mutable.ListBuffer.empty[PartitionAndReplica] val allReplicasForDeletionRetry = mutable.ListBuffer.empty[PartitionAndReplica] val allTopicsIneligibleForDeletion = mutable.Set.empty[String]
val successfullyDeletedReplicas = controllerContext.replicasInState(topic, ReplicaDeletionSuccessful) val replicasForDeletionRetry = aliveReplicas.diff(successfullyDeletedReplicas)