ControllerContext储存kafka集群的元数据,定义如下:
class ControllerContext {
val stats = new ControllerStats // Controller统计信息类
var offlinePartitionCount = 0 // 离线分区计数器
var shuttingDownBrokerIds: mutable.Set[Int] = mutable.Set.empty // 关闭中Broker的Id列表
private var liveBrokers: Set[Broker] = Set.empty // 当前运行中Broker对象列表
private var liveBrokerEpochs: Map[Int, Long] = Map.empty // 运行中Broker Epoch列表
var epoch: Int = KafkaController.InitialControllerEpoch // Controller当前Epoch值
var epochZkVersion: Int = KafkaController.InitialControllerEpochZkVersion // Controller对应ZooKeeper节点的Epoch值
// Kafka 使用 epochZkVersion 来判断和防止 Zombie Controller。
// 这也就是说,原先在老 Controller 任期内的 Controller 操作在新 Controller 不能成功执行,因为新 Controller 的 epochZkVersion 要比老 Controller 的大。
var allTopics: Set[String] = Set.empty
val partitionAssignments = mutable.Map.empty[String, mutable.Map[Int, ReplicaAssignment]] // 主题分区的副本列表
val partitionLeadershipInfo = mutable.Map.empty[TopicPartition, LeaderIsrAndControllerEpoch] // 主题分区的Leader/ISR副本信息
val partitionsBeingReassigned = mutable.Set.empty[TopicPartition] // 正处于副本重分配过程的主题分区列表
val partitionStates = mutable.Map.empty[TopicPartition, PartitionState] // 主题分区状态列表
val replicaStates = mutable.Map.empty[PartitionAndReplica, ReplicaState] // 主题分区的副本状态列表
val replicasOnOfflineDirs: mutable.Map[Int, Set[TopicPartition]] = mutable.Map.empty // 不可用磁盘路径上的副本列表
val topicsToBeDeleted = mutable.Set.empty[String] // 待删除主题列表
val topicsWithDeletionStarted = mutable.Set.empty[String] // 已开启删除的主题列表
val topicsIneligibleForDeletion = mutable.Set.empty[String] // 暂时无法执行删除的主题列表
}
ControllerStats
第一个是 ControllerStats 类的变量。它的完整定义如下:
// ControllerStats Controller 的一些统计信息.
// 目前,源码中定义了两大类统计指标:UncleanLeaderElectionsPerSec 和所有 Controller 事件状态的执行速率与时间。
private[controller] class ControllerStats extends KafkaMetricsGroup {
// 统计每秒发生的Unclean Leader选举次数
// 通常情况下,执行 Unclean Leader 选举可能造成数据丢失,一般不建议开启它。
val uncleanLeaderElectionRate = newMeter("UncleanLeaderElectionsPerSec", "elections", TimeUnit.SECONDS)
val rateAndTimeMetrics: Map[ControllerState, KafkaTimer] = ControllerState.values.flatMap { state =>
// Controller事件通用的统计速率指标的方法
// 由于 Controller 事件有很多种,对应的速率监控指标也有很多,有一些 Controller 事件是需要你额外关注的。
// 举个例子,IsrChangeNotification 事件是标志 ISR 列表变更的事件,如果这个事件经常出现,说明副本的 ISR 列表经常发生变化,而这通常被认为是非正常情况。
state.rateAndTimeMetricName.map { metricName =>
state -> new KafkaTimer(newTimer(metricName, TimeUnit.MILLISECONDS, TimeUnit.SECONDS))
}
}.toMap
}
offlinePartitionCount
该字段统计集群中所有离线或处于不可用状态的主题分区数量。ControllerContext 中的 updatePartitionStateMetrics
方法根据给定主题分区的当前状态和目标状态,来判断该分区是否是离线状态的分区。如果是,则累加 offlinePartitionCount 字段的值,否则递减该值。
private def updatePartitionStateMetrics(partition: TopicPartition,
currentState: PartitionState,
targetState: PartitionState): Unit = {
// 如果该主题当前并未处于删除中状态
if (!isTopicDeletionInProgress(partition.topic)) {
// targetState表示该分区要变更到的状态
// 如果当前状态不是OfflinePartition,即离线状态并且目标状态是离线状态
// 这个if语句判断是否要将该主题分区状态转换到离线状态
if (currentState != OfflinePartition && targetState == OfflinePartition) {
offlinePartitionCount = offlinePartitionCount + 1
// 如果当前状态已经是离线状态,但targetState不是
// 这个else if语句判断是否要将该主题分区状态转换到非离线状态
} else if (currentState == OfflinePartition && targetState != OfflinePartition) {
offlinePartitionCount = offlinePartitionCount - 1
}
}
}
shuttingDownBrokerIds
该字段保存所有正在关闭中的 Broker ID 列表,依靠这个字段来甄别 Broker 当前是否已关闭,因为处于关闭状态的 Broker 是不适合执行某些操作的。
private def onBrokerFailure(deadBrokers: Seq[Int]): Unit = {
info(s"Broker failure callback for ${deadBrokers.mkString(",")}")
// deadBrokers:给定的一组已终止运行的Broker Id列表
// 更新Controller元数据信息,将给定Broker从元数据的replicasOnOfflineDirs中移除
deadBrokers.foreach(controllerContext.replicasOnOfflineDirs.remove)
// 找出这些Broker上的所有副本对象
val deadBrokersThatWereShuttingDown =
deadBrokers.filter(id => controllerContext.shuttingDownBrokerIds.remove(id))
if (deadBrokersThatWereShuttingDown.nonEmpty)
info(s"Removed ${deadBrokersThatWereShuttingDown.mkString(",")} from list of shutting down brokers.")
// 执行副本清扫工作
val allReplicasOnDeadBrokers = controllerContext.replicasOnBrokers(deadBrokers.toSet)
onReplicasBecomeOffline(allReplicasOnDeadBrokers)
// 取消这些Broker上注册的ZooKeeper监听器
unregisterBrokerModificationsHandler(deadBrokers)
}
liveBrokers
该字段保存当前所有运行中的 Broker 对象。ControllerContext 中定义了很多方法来管理该字段,如 addLiveBrokersAndEpochs、removeLiveBrokers 和 updateBrokerMetadata 等。
// 每个 Broker 对象就是一个 <Id,EndPoint,机架信息 > 的三元组。
def addLiveBrokersAndEpochs(brokerAndEpochs: Map[Broker, Long]): Unit = {
liveBrokers = liveBrokers ++ brokerAndEpochs.keySet
liveBrokerEpochs = liveBrokerEpochs ++
(brokerAndEpochs map { case (broker, brokerEpoch) => (broker.id, brokerEpoch)})
}
def removeLiveBrokers(brokerIds: Set[Int]): Unit = {
liveBrokers = liveBrokers.filter(broker => !brokerIds.contains(broker.id))
liveBrokerEpochs = liveBrokerEpochs.filter { case (id, _) => !brokerIds.contains(id) }
}
def updateBrokerMetadata(oldMetadata: Broker, newMetadata: Broker): Unit = {
liveBrokers -= oldMetadata
liveBrokers += newMetadata
}
liveBrokerEpochs
该字段保存所有运行中 Broker 的 Epoch 信息。Kafka 使用 Epoch 数据防止 Zombie Broker,即一个非常老的 Broker 被选举成为 Controller。
def liveBrokerIds: Set[Int] = liveBrokerEpochs.keySet -- shuttingDownBrokerIds
partitionAssignments
该字段保存所有主题分区的副本分配情况。
比如,如果 Kafka 要获取某个 Broker 上的所有分区,那么,它可以这样定义
partitionAssignments.flatMap {
case (topic, topicReplicaAssignment) => topicReplicaAssignment.filter {
case (_, partitionAssignment) => partitionAssignment.replicas.contains(brokerId)
}.map {
case (partition, _) => new TopicPartition(topic, partition)
}
}.toSet