https://www.cnblogs.com/boanxin/p/13618431.html
1、概念
控制器组件(Controller),是 Apache Kafka 的核心组件。
Controller的主要作用是在Apache Zookeeper的帮助下管理和协调控制整个Kafka集群,管理元数据,管理集群状态。
集群中任意一台 Broker都能充当控制器的角色,在运行过程中,有且只有一个 Broker 成为控制器,行使其管理和协调的职责。
2、集群元数据
集群的元数据信息都保存在ControllerContext类中,包含以下属性
class ControllerContext {
val stats = new ControllerStats // Controller统计信息类
var offlinePartitionCount = 0 // 离线分区计数器
var preferredReplicaImbalanceCount = 0
val shuttingDownBrokerIds = mutable.Set.empty[Int] // 关闭中Broker的Id列表
private val liveBrokers = mutable.Set.empty[Broker] // 当前运行中Broker对象列表
private val liveBrokerEpochs = mutable.Map.empty[Int, Long] // 运行中Broker Epoch列表
var epoch: Int = KafkaController.InitialControllerEpoch // Controller当前Epoch值
var epochZkVersion: Int = KafkaController.InitialControllerEpochZkVersion // Controller对应ZooKeeper节点的Epoch值
val allTopics = mutable.Set.empty[String] // 集群主题列表
val partitionAssignments = mutable.Map.empty[String, mutable.Map[Int, ReplicaAssignment]] // 主题分区的副本列表
private 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.empty[Int, Set[TopicPartition]] // 不可用磁盘路径上的副本列表
val topicsToBeDeleted = mutable.Set.empty[String] // 待删除主题列表
val topicsWithDeletionStarted = mutable.Set.empty[String] // 已开启删除的主题列表
val topicsIneligibleForDeletion = mutable.Set.empty[String] // 暂时无法执行删除的主题列表
}
3、Controller组成结构
- zk监听器
- ScheduledTask 定时任务
auto.leader.rebalance.enable参数设为true,那么就会启动名为auto-leader-rebalance-task的定时任务来自动维护最优Replica的平衡
- ControllerContext 集群元数据上下文对象
- Event Queue 事件队列
为FIFO的阻塞队列(LinkedBlockingQueue),承载各个监听器线程、定时任务线程投递过来的状态变更信息,这些信息都包装为事件。
单线程事件队列模型:多个线程向事件队列写入不同种类的事件,而消费处理队列只有一个线程
- Event Executor事件处理线程
单线程处理各个事件,并将它们的结果更新到ControllerContext,以及异步地广播到各个Broker中。
使用单线程的好处是无需关心多线程的同步,无锁机制可以提升性能
4、Controller向broker的请求发送
controller组件启动时(ControllerChannelManager.startup()),从元数据信息中找到集群的Broker列表,为集群上的每个Broker(包括controller自己所在的broker)都创建一个对应的RequestSendThread线程, 这个线程中维护了个阻塞队列,Controller Event ExecutorThread 负责向这个队列写入待发送的请求 ,RequestSendThread线程会持续地从阻塞队列中获取待发送的请求,发出请求后,阻塞等待response返回调用callback执行回调,完成后,该线程才能从阻塞队列中取出下一个待发送请求进行处理。
class RequestSendThread(val controllerId: Int, //Controller所在Broker的Id
val controllerContext: ControllerContext, //元数据
val queue: BlockingQueue[QueueItem], //请求阻塞队列
val networkClient: NetworkClient, // 用于执行发送的网络I/O类
val brokerNode: Node, // 目标Broker节点
val config: KafkaConfig, // Kafka配置信息
val time: Time,
val requestRateAndQueueTimeMetrics: Timer,
val stateChangeLogger: StateChangeLogger,
name: String)
extends ShutdownableThread(name = name) {
private val socketTimeoutMs = config.controllerSocketTimeoutMs
override def doWork(): Unit = {
def backoff(): Unit = pause(100, TimeUnit.MILLISECONDS)
val QueueItem(apiKey, requestBuilder, callback, enqueueTimeMs) = queue.take()
requestRateAndQueueTimeMetrics.update(time.milliseconds() - enqueueTimeMs, TimeUnit.MILLISECONDS)
try {
...
val clientRequest = networkClient.newClientRequest(brokerNode.idString, requestBuilder, time.milliseconds(), true)
clientResponse = NetworkClientUtils.sendAndReceive(networkClient, clientRequest, time)
...
if (callback != null) {
callback(response)
}
...
}
} catch {
...
}
目前controller只会向broker发送3类请求:
-
LeaderAndIsrRequest
告诉broker相关主题各个分区的leader副本位于那台Broker上,ISR的副本在那些broker上 -
StopReplicaRequest
告知指定broker停止它上面的副本对象,主要用于分区副本迁移和删除主题的操作 -
UpdateMetadataRequest
该请求会更新Broker上的元数据缓存,集群上的所有元数据变更都首先发生在Controller端,然后在经由这个请求广播给集群上的所有broker
5、Controller选举
Kafka 选举控制器的规则是:第一个成功创建 /controller 临时节点的 Broker 会被指定为控制器。
控制器在选举成功之后会读取Zookeeper中各个节点的数据来初始化上下文信息(ControllerContext)
临时节点内容 {“version”:1,“brokerid”:0,“timestamp”:“1608173427409”}
version在目前版本中固定为1,brokerid表示称为控制器的broker的id编号,timestamp表示竞选称为控制器时的时间戳。
触发 Controller 选举的三个场景:
- 集群首次启动时
启动时首先将 Startup 这个 ControllerEvent写入到事件队列中,然后启动对应的事件处理线程和、注册监听器zkClient.registerZNodeChangeHandlerAndCheckExistence(controllerChangeHandler),最后依赖事件处理线程进行 Controller 的选举,调用 elect 方法
- Broker 检测到 /controller 节点消失时
当Controller 宕机时,对应的临时节点/controller被删除,所有检测到 /controller 节点消失的Broker,都会立即调用 elect 方法执行竞选逻辑
- Broker 检测到 /controller 节点数据发生变化
比如:当前的controller所在的broker从ISR中移除了或者Controller易主了
对于这种Controller“易主”的情况又分为两种:
- 如果 Broker 之前是 Controller,那么该 Broker 需要首先执行卸任操作(用于清空各种数据结构的值、取消 ZooKeeper 监听器、关闭各种状态机以及管理器),然后再尝试竞选;
- 如果 Broker 之前不是 Controller,那么该 Broker 直接去竞选新 Controller。
6、Controller epoch
Zookeeper中还有一个与控制器有关的/controller_epoch节点,这个节点是持久(PERSISTENT)节点,节点中存放的是一个整型值。
脑裂问题:
如果Controller所在的Broker只是暂时故障而非宕机(比如FullGC STW了),Kafka集群为了使集群正常运行重新选举Controller,选举完成后。之前的Controller又正常了,它将继续充当当前Controller,那么集群中就会出现两个Controller
又或者controller向broker广播了leader发生变更。于是新leader开始接收clients端请求,而同时老leader所在的broker由于出现了数据类请求的积压使得它一直忙于处理这些请求而无法处理controller发来的LeaderAndIsrRequest请求,集群中就会出现两个Controller
对于集群中出现多个leader的情况称为裂脑;
controller_epoch用于记录控制器发生变更的次数,初始值为1。当控制器发生变更时,每选出一个新的控制器就将该字段值加1。每个和控制器交互的请求都会携带上controller_epoch这个字段,如果请求的controller_epoch值小于内存中的controller_epoch值,那么这个请求会被认定为无效的请求。如果请求的controller_epoch值大于内存中的controller_epoch值,那么则说明已经有新的控制器当选了,自身进行卸任。通过比较epoch区分最新的Controller解决脑裂问题。
7、Controller 职责
身为Controller的Broker比其它的Broker多一些职责,具体为下
监听分区,分区重分配
- 注册PartitionReassignmentListener监听器,监听ZK的/admin/reassign_partitions节点,用于重新分配各Partition的Leader和Follower。
- 注册IsrChangeNotificetionListener监听器,监听ZK的/isr_change_notification节点,处理ISR集合发生变化的Partition。
- 注册PreferredReplicaElectionListener监听器,监听ZK的/admin/preferred_replica_election节点,处理最优Replica的重选举。
监听主题,主题管理
- 注册TopicChangeListener监听器,监听ZK的/brokers/topics节点,处理Topic的增删操作;
- 注册TopicDeletionListener监听器,监听ZK的/admin/delete_topics节点,用来实际执行删除Topic的动作。
监听Broker,集群成员管理
- 注册BrokerChangeListener监听器,监听ZK的/brokers/ids节点,触发Broker的上下线操作。
- 注册PartitionModificationsListener监听器,监听ZK中各个/brokers/topics/节点,处理Topic的Partition扩容和缩容操作。
- 启动并管理Partition状态机组件(PartitionStateMachine)和Replica状态机组件(ReplicaStateMachine)。
数据服务
- 从ZK中获取Broker、Topic、Partition相关的元数据信息
- 更新集群的元数据信息,将最新元数据广播给各个Broker