在启动Kafka集群时,每一个代理都会实例化并启动一个KafkaController,并将该代理的brokerId注册到Zookeeper的相应节点中。Kafka集群中各代理会根据选举机制选出其中一个代理作为Leader,称之为Leader控制器。
控制器负责主题的创建与删除,分区和副本的管理,代理故障转移以及代理上下线处理等。控制器功能实现的程序入口是在core工程下的kafka.controller.KafkaController类。
一、控制器初始化
控制器在初始化时主要完成以下工作:
1. 创建一个ControllerContext实例对象
用于缓存控制器各种处理操作所需要的数据结构,初始化控制器选举次数epoch以及对应的zkVersion字段为0,设置当前正常运行的代理列表,主题列表,分区与副本的AR列表,同时实例化代理选举控制器操作的ReentrantLock。
2. 创建分区状态机
用于维护和管理分区状态。
3. 创建副本状态机
用于管理副本状态。
4. 创建ZooKeeperLeaderElector选举器对象
用于将当前代理选举为控制器。一个监听:LeaderChangeListener,两个操作:
- 完成控制器相应初始化的onControllerFailover()方法
- 当新的控制器当选时让先前的控制器注销控制器权限的onControllerResignation()方法
5. 创建定时任务KafkaScheduler
用于控制器进行平衡操作,其生命周期只在代理成为Leader控制器期间内有效。
6. 创建TopicDeletionManager对象
用于对主题操作管理。
7. 创建分区选举器PartitionLeaderSelector
用于在分区状态发生变化时为分区选举出Leader副本的分区选举器。
8. 实例化ControllerBrokerRequestBatch
前面创建了分区状态机和副本状态机,这两个状态机在相应状态发生变化时相应监听器都会调用各自的handleStateChange()方法进行处理,而ControllerBrokerRequestBatch封装了leaderAndIsrRequestMap、stopReplicaRequestMap和updateMetadataRequestMap这3个集合,用来记录和缓存handleStateChange()方法中产生的request,控制器将这些request交由ControllerBrokerRequestBatch.sendRequestsToBrokers()方法批量发送出去,交由KafkaApis调用相应的handle方法进行处理。
9. 实例化3个监听器
PartitionsReassignedListener:用于监听分区重分配。
PreferredReplicaElectionListener:用于监听当分区状态变化时选举器将优先副本选举为Leader。
IsrChangeNotificationListener:用于监听ISR列表变化,所有代理节点更新元数据。
总结下控制器初始化工作涉及的组件与作用如下图所示:
当一个代理启动时就会创建一个KafkaController实例并启动。在启动KafkaController时,先注册一个用于监听代理与ZooKeeper会话超时的监听器SessionExpirationListener,然后启动控制器选举,让当前代理试图去竞选为控制器。
二、控制器选举流程
控制器选举的时机:
- 控制器启动
- 控制器故障转移
- 心跳检测超时
控制器选举依赖于Zookeeper,在集群运行过程中,首先注册一个LeaderChangeListener,负责监听Zookeeper的/controller节点数据变化,当代理当选为Leader控制器后,当前代理上任,注册Leader拥有的权限和启动相应工作流程,当代理不再担任Leader时,则进行代理退位,注销Leader拥有的权限。
控制器选举算法的入口为ZooKeeperLeaderElector.select()方法,该方法执行逻辑是:每个代理首先从zk的/controller节点获取Leader信息,解析当前Leader的leaderId,即作为Leader的代理的brokerId。如leaderId等于-1,则表示还没有成功选举出Leader,则该代理将自己的brokerId信息写入到/controller节点中,若写入成功,则当前代理称为Leader。如果leaderId不等于-1,则表示已有代理抢先成为了Leader,则停止选举。
利用Zookeeper的强一致性特性,一个节点只能被一个客户端创建成功,创建成功的broker即为leader,即先到先得原则。当leader和zookeeper失去连接时,临时节点会删除,而其他broker会监听该节点的变化,当节点删除时,其他broker会收到事件通知,重新发起leader选举
如果选举过程中发生异常,则会将leaderId设置为-1,同时删除存储在/controller节点的元数据信息,触发Leader的重新选举。控制器选举流程如下图所示:
在选举中,controller_epoch称之为控制器轮值次数,用于记录控制器变更次数,初始值为0,每选出一个新的控制器将该字段加1。如果请求的controller_epoch值小于内存中controller_epoch的值,本次请求无效,认为这个请求是向已过期的控制器发送的请求;如果该值大于内存中的controller_epoch值,则说明已有新的控制器当选了。通过该值保证集群控制器的唯一性,进而保证相关操作的一致性。与controller_epoch相似的有一个leader_epoch,是相对于分区来说的,记录分区Leader更新的次数。
三、控制器故障转移
故障转移指的是Leader所在broker发生故障,Leader转移到其他的broker,也就是重新选举出新的控制器。故障转移时要为不再是Leader的控制器注销相应的权限,调用的是onControllerResignation方法,同时,为新当选的Leader控制器注册相应的权限,调用的是onControllerFailover方法。
onControllerFailover初始化流程
首先从zk中获取当前控制器的轮值次数,将控制器轮值次数加1,再更新到zk的/controller_epoch节点中。确保控制器轮值次数处理完成之后,再进行一系列的初始化工作。
1. 注册分区管理相关的监听器
监听名称 | 监听zk节点 | 作用 |
PartitionsReassignedListener | /admin/reassign_partitions | 节点引发分区重分配操作 |
IsrChangeNotificationListener | /isr_change_notification | 处理分区ISR变化引发的操作 |
PreferredReplicaElectionListener | /admin/preferred_replica_election | 将优先副本选举为Leader副本 |
2. 注册主题管理的监听器
监听名称 | 监听zk节点 | 作用 |
TopicChangeListener | /brokers/topics | 监听主题发生变化时进行相应的处理操作 |
DeleteTopicsListener | /admin/delete_topics | 完成服务器端删除主题相应的操作,否则当客户端删除一个主题时,仅是将该主题标识为删除,但服务端并没有将该主题真正删除。 |
3. 注册代理变化处理的监听器
监听名称 | 监听zk节点 | 作用 |
BrokerChangeListener | /brokers/ids | 当代理发生增、减变化时进行相应的处理 |
4. 初始化ControllerContext
初始化存活的代理集合、主题集合及每个分区的AR集合信息,更新ControllerContext中每个分区的Leader及ISR信息。创建一个用于管理主题删除操作的TopicDeletionManager对象。
5. 启动分区状态机和副本状态机
6. 为主题添加监听
从ControllerContext中读取所有主题,轮询每个主题,为每个主题添加用于监听分区变化的PartitionModificationsListener。
7. 分区重分配
检测当前是否有分区需要触发分区重分配操作。若需要重分配,则进行一次分区重分配操作。
8. 优先副本选举
检测当前是否有需要将优先副本选举为Leader的分区,并进行相应的操作。
9. 更新元数据
向Kafka集群中所有存活的代理发送更新元数据请求。
10. 创建分区重分配定时任务
根据配置auto.leader.rebalance.enable决定是否创建用于分区平衡操作的定时任务。默认是300秒,即每5分钟执行一次分区重分配检查及分区重分配操作的定时任务。
11. 启动删除主题组件
启动第4步创建的删除主题管理的TopicDeletionManager组件。
onControllerResignation注销流程
- 取消该控制器在zk中注册的用于对分区以及副本变化感知的监听器的监听
- 关闭删除主题操作
- 关闭分区平衡操作的定时任务
- 取消对ISR变化的监听
- 关闭分区状态机和副本状态机
- 关闭控制器与其他代理之间的通信
- 重置控制器轮值次数置为0
- 将当前代理状态设置为RunningAsBroker
至此,当前代理就不再是控制器角色。
四、代理上下线
集群中加入新的broker,称代理上线,当broker关闭,退出集群时,称代理下线。代理上下线即是对BrokerChangeListener所做的处理进监听的处理。
代理上线
新上线的代理在启动时会向/brokers/ids节点下注册该代理的brokerId,此时会被副本状态机在zk所注册的BrokerChangeListener监听到该节点的变化。代理上线的具体流程:
1. 从zk中的节点信息中获取到新上线的节点集合
2. 对新上线的代理节点调用ControllerChannelManager.addBroker方法完成新上线代理网络层相关初始化处理
3. 调用KafkaController.onBrokerStartup方法启动新上线的节点,具体逻辑如下:
- 向集群当前所有的代理发送UpdateMetadataRequest请求,告知有新的代理加入
- 通过副本状态机对副本和分区进行变迁
- 触发一次分区Leader选举,以确认新增加的代理是否是分区的Leader
- 轮询分配给新上线的代理的副本,调用KafkaController.onPartitionReassignment方法执行服务端分区副本分配操作
- 恢复由于新代理上线而被暂停的删除主题的操作,让其继续完成服务端删除主题的操作
代理下线
当代理下线时,该代理在ZooKeeper的/brokers/ids节点注册的与该代理对应的节点将被删除,同时触发BrokerChangeListener的handleChildChange方法。代理下线具体流程:
1. 查找下线节点的集合
2. 轮询下线节点,关闭每个下线节点的网络连接,清空下线节点的消息队列,关闭下线节点发送Request请求的线程
3. 调用KafkaController的onBrokerFailure方法进行节点下线,具体逻辑如下:
- 查找Leader副本在下线节点上的分区,重新选举Leader副本,发送请求更新元数据信息
- 查找所有下线节点上的副本集合,对于待删除的主题副本,标记为暂不可删除
- 对于正常使用的主题副本,从分区的ISR集合中删除,不再与Leader副本同步
- 发送UpdateMetadataRequest请求,更新当前所有存活代理的缓存的元数据信息
- 向集群所有存活的代理发送UpdateMetadataRequest请求,执行元数据更新操作