kafka源码分析—Controller

概述

本文描述了kafka的controller的实现原理,并对其源代码的实现进行了讲解。

controller运行原理

在Kafka集群中,controller多个broker中的一个(也只有一个controller),它除了实现正常的broker的功能外,还负责选取分区(partition)的leader。
第一个启动的broker会成为一个controller,它会在Zookeeper上创建一个临时节点(ephemeral):/controller。其他后启动的broker也尝试去创建这样一个临时节点,但会报错,此时这些broker会在该zookeeper的/controller节点上创建一个监控(Watch),这样当该节点状态发生变化(比如:被删除)时,这些broker就会得到通知。此时,这些broker就可以在得到通知时,继续创建该节点。保证该集群一直都有一个controller节点。

当controller所在的broker节点宕机或断开和Zookeeper的连接,它在Zookeeper上创建的临时节点就会被自动删除。其他在该节点上都安装了监控的broker节点都会得到通知,此时,这些broker都会尝试去创建这样一个临时的/controller节点,但它们当中只有一个broker(最先创建的那个)能够创建成功,其他的broker会报错:node already exists,接收到该错误的broker节点会再次在该临时节点上安装一个watch来监控该节点状态的变化。每次当一个broker被选举时,将会赋予一个更大的数字(通过zookeeper的条件递增实现),这样其他节点就知道controller目前的数字。

当一个broker宕机而不在当前Kafka集群中时,controller将会得到通知(通过监控zookeeper的路径实现),若有些topic的主分区恰好在该broker上,此时controller将重新选择这些主分区。controller将会检查所有没有leader的分区,并决定新的leader是谁(简单的方法是:选择该分区的下一个副本分区),并给所有的broker发送请求。

每个分区的新leader指导,它将接收来自客户端的生产者和消费者的请求。同时follower也指导,应该从这个新的leader开始复制消息。
当一个新的broker节点加入集群时,controller将会检查,在该broker上是否存在分区副本。若存在,controller通知新的和存在的broker这个变化,该broker开始从leader处复制消息。

总的来说,Kafka会通过在Zookeeper上创建临时节点的方式来选举一个controller,但Kafka集群中有节点加入或退出时,该controller将会得到通知。Controller还负责在多个分区中选择主分区,负责当有节点加入集群时进行副本的复制。Controller通过递增数字(epoch number)来防止脑裂(split brain)的问题(脑裂是指:多个节点都选自己为Controller)。

实现分析

Controller的实现原理

Controller是通过事件处理机制来实现的。把broker的节点的变化,分区的变化,都封装成事件,发生事件时把事件放入到事件队列中,此时阻塞在事件队列的处理者即可开始处理这些事件。
这些事件类,都必须实现同一个接口。

启动

类KafkaServer中的startup()函数中启动Controller,代码如下:

def startup() {
    ...
    /* start kafka controller */
    kafkaController = new KafkaController(config, zkClient, time, metrics, brokerInfo, tokenManager, threadNamePrefix)
    // 启动kafka server的控制模块
    kafkaController.startup()
    ...
}

初始化工作

Kafka服务节点启动时Controller模块就会启动。但当Controller启动时,不会假设自己是controller,而是先注册回话超时的监听者(listener),然后开始controller的leader选举过程。

启动时,会把Startup事件控制实体,放入到事件队列中。在eventManager线程启动时,会在队列取出ControllerEvent类型的事件。并进行处理,此时取出的当然是刚刚放入的Startup事件,所以,开始执行Startup类的process函数。代码实现如下:

def startup() = {
    ... ...
    eventManager.put(Startup)
    eventManager.start()
  }
  • Startup事件控制实体
    在Controller模块启动时,该事件就被放入到事件队列中,所以,最开始处理该事件。执行的处理函数是下面定义的process()。代码的实现如下:
case object Startup extends ControllerEvent {
    def state = ControllerState.ControllerChange
    override def process(): Unit = {
      zkClient.registerZNodeChangeHandlerAndCheckExistence(controllerChangeHandler)
      elect()
    }

  }

Startup事件处理

private def elect(): Unit = {
    val timestamp = time.milliseconds
    activeControllerId = zkClient.getControllerId.getOrElse(-1)

    // 这里要判断一下controller是否已经选出来了,若是选出来了,就不需要再继续选举
    if (activeControllerId != -1) {
      debug(s"Broker $activeControllerId has been elected as the controller, so stopping the election process.")
      return
    }

    // 若controller还没有选出来,则进行选举
    try {
        // 若成功的选举成controller,继续进行后面的注册,否则抛出异常
      zkClient.checkedEphemeralCreate(ControllerZNode.path, ControllerZNode.encode(config.brokerId, timestamp))
      info(s"${config.brokerId} successfully elected as the controller")
      // 自己被选举成controller,进入onControllerFailover函数
      activeControllerId = config.brokerId
      onControllerFailover()
    } catch {
      case _: NodeExistsException =>
        // If someone else has written the path, then
        activeControllerId = zkClient.getControllerId.getOrElse(-1)

        ... ...
    }
  }

被选举成controller之后

当broker被选举成为controller后,继续执行后面的代码,此时进入onControllerFailover()函数。
该函数主要完成以下几件事:
* 注册controller epoch(controller id)变化的监听器
* 增加controller的id
* 初始化controller的context,该context保存了每个topic信息,和所有分区的leader信息。
* 开启controller的channel管理模块
* 开启副本状态机
* 开启分区状态机
* 当该发生任何的异常,会从新选择目前的controller的,这样让其他的broker节点也有可能成为controller。
* 注册zk上的broker状态变化,topic状态变化,topic删除,分区的zk目录等状态变化时的处理函数。

总结

本文总结了controller的运行原理,通过本文可以理解,controller的功能和选举过程。

猜你喜欢

转载自blog.csdn.net/zg_hover/article/details/81672997