在启动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列表变化,所有代理节点更新元数据。

总结下控制器初始化工作涉及的组件与作用如下图所示:

rdkafka 监听多个主题只能收到一个主题_初始化

当一个代理启动时就会创建一个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的重新选举。控制器选举流程如下图所示:

rdkafka 监听多个主题只能收到一个主题_初始化_02

在选举中,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请求,执行元数据更新操作