KafkaController 启动
在KafkaServer.startup()中,KafkaController对象被构建,在启动KafkaApis、replicaManager后,KafkaController.startup()
被调用。Kafka集群中每个Broker都会调用startup()
函数,但是一个集群只有一个Broker能够成为Controller。那么,谁将成为这个被选中的人呢?
startup()
函数非常简单,这里直接粘代码:
def startup() = {
inLock(controllerContext.controllerLock) {
info("Controller starting up");
registerSessionExpirationListener()
isRunning = true
controllerElector.startup
info("Controller startup complete")
}
}
除去日志以及标识状态的isRunning
赋值,值得看的代码就两句。其中registerSessionExpirationListener()
用于在zookeeper会话失效后重连时取消注册在zookeeper上的各种Listener,而controllerElector.startup
则启动了选举,这些都将发生在ZookeeperLeaderElector类中。
ZookeeperLeaderElector.elect
函数负责选举leader,KafkaController选举是直接通过zookeeper实现的,就是在zookeeper创建临时目录/controller/并在目录下存放当前brokerId。如果在zookeeper下创建路径没有抛出ZkNodeExistsException异常,则当前broker成功晋级为Controller。除了调用elect
外,controllerElector.startup
还会在/controller/路径上注册Listener,监听dataChange事件和dataDelete事件,当/controller/下数据发生变化时,表示Controller发生了变化;而因为/controller/下的数据为临时数据,当Controller发生failover时,数据会被删除,触发dataDelete事件,这时就需要重新选举新一任Controller。
成为KafkaController
成为KafkaController之后很重要的一件事,就是在zookeeper各个关键路径上添加Listener,所以这里很有必要先总结一下跟controller相关的路径([ ]表示其中的值是随实际情况变化的):
- /controller/{“brokerid”:”1”}: 决定谁才是这一届Controller,路径下存放当前Controller的brokerId,这些信息以临时数据的形式存放,在会话失效时会被删除。类LeaderChangeListener监听该路径下的dataChange和dataDelete事件。
- /brokers/topics:子目录为所有topic列表。类TopicChangeListener监听子目录列表变化,如果有新增topic,则调用onNewTopicCreation创建新的topic。
- /brokers/topics/[topic]/:存放的是topic下各个分区的AR,目录下存放的格式为{“partitions”:{“partitionId1”:[broker1,broker2], …}}。类AddPartitionsListener监听路径下的数据变化,在有新增partition时调用Controller.onNewPartitionCreate,即创建新的partition。
- /brokers/topics/[topic]/partitions/[partitionId]/state/:存放的是各个分区的leaderAndIsr信息,即各个分区当前的leaderId,以及ISR。类ReassignedPartitionsIsrChangeListener监听该路径下的数据变化,在重新分配replica到partition时,需要等待新的replica追赶上leader后才能执行后续操作。
- /brokers/ids/[brokerId]/brokerInfoString:存放broker信息,brokerInfoString包括broker的IP,端口等信息;BrokerChangeListener监听/brokers/ids/下子目录变化,从而通知Controller broker的上下线消息。brokerInfoString是Controller判断的broker是否活着的条件之一,controllerContext中的liveBrokers需要相应路径下能够获取到brokerInfo。
- /admin/reassign_partitions:指导重新分配AR的路径,通过命令修改AR时会写入到这个路径下。类PartitionsReassignedListener监听该路径下的内容变化,调用initiateReassignReplicasForTopicPartition,执行重新分配AR操作。
- /admin/preferred_replica_election:分区需要重新选举第一个replica作为leader,即所谓的preferred replica。类PreferredReplicaElectionListener监听该路径,并对路径下的partitions执行重新选举preferred replica作为leader。
进入正题,成为KafkaController以后,会执行什么操作呢?
1. 升级Controller Epoch,并将新的epoch写入到zookeeper中;新的epoch标识着下一个世代的leader,向其他broker发送命令时会校验epoch;
2. 监听zookeeper路径/admin/reassign_partitions
;
3. 监听zookeeper路径/admin/preferred_replica_election
;
4. 注册partition状态机中的监听器,监听路径/brokers/topics
的子目录变化,随时准备创建topic;
5. 注册replica状态机中的监听器,监听路径/brokers/ids/
的子目录,以便在新的broker加入时能够感知到;
6. 初始化ControllerContext,主要是从zookeeper中读取数据初始化context中的变量,诸如 liveBrokers,allTopics,AR,LeadershipInfo等;
7. 初始化ReplicaStateMachine,将所有在活跃broker上的replica的状态变为OnlineReplica;
8. 初始化PartitionStateMachine,将所有leader在活跃broker上的partition的状态设置为Onlinepartition;其他的partition状态为OfflinePartition。Partition是否为Online的标识就是leader是否活着;之后还会触发OfflinePartition 和 NewPartition向OnlinePartition转变,因为OfflinePartition和NewPartition可能是选举leader不成功,所以没有成为OnlinePartition,在环境变化后需要重新触发;
9. 在所有的topic的zookeeper路径/brokers/topics/[topic]/
上添加AddPartitionsListener,监听partition变化;
完成对各个zookeeper路径的监听后,zookeeper内容的变化驱动Controller进行各种操作,处理如新建topic,删除topic,broker失效,broker恢复等事件。
Controller Failover
前面startup()中registerSessionExpirationListener()
会注册会话监听器,在zookeeper会话过期后又重连成功时调用onControllerResignation(),并重新执行选举操作。
此外,当Controller会话失效时,会删除/controller/
路径下创建的临时数据。与此同时,其他broker上的ZookeeperLeaderElector类中的LeaderChangeListener感知到数据删除后会重新执行选举。
onControllerResignation()是Controller转变为普通broker时执行的操作,就是将前面注册的各个Listener取消注册,不再关注zookeeper变化。