Kafka的leader选举过程

上一篇文章说到,当创建一个topic时,kafka是如何分配partition和replica的.

这篇文章本想讨论在topic创建完毕后,当broker信息发生变化,kafka是如何处理partition和replica的分配的.不过我读了源码之后,感觉首先要理清leader和follower之间的关系.

因此我决定先写一篇文章描述一下Kafka的leader选举过程,kafka源码的版本是0.10.1.


首先我们要知道,消息从哪里来?即kafka集群怎么知道一台broker的加入,离开或者崩溃的.

上一篇文章我提到了,kafka集群本身会通过Zookeeper选举出一个leader和若干个follower.在kafka.server这个package下有一个kafkaController.scala类,每一台broker在初始化时都会初始化kafkaContoller对象,在kafkaController初始化时,会进行leader选举就是下面代码的最后一行,new ZookeeperLeaderElector()

class KafkaController(val config : KafkaConfig, zkUtils: ZkUtils, val brokerState: BrokerState, time: Time, metrics: Metrics, threadNamePrefix: Option[String] = None) extends Logging with KafkaMetricsGroup {
  this.logIdent = "[Controller " + config.brokerId + "]: "
  private var isRunning = true
  private val stateChangeLogger = KafkaController.stateChangeLogger
  val controllerContext = new ControllerContext(zkUtils, config.zkSessionTimeoutMs)
  val partitionStateMachine = new PartitionStateMachine(this)
  val replicaStateMachine = new ReplicaStateMachine(this)
  private val controllerElector = new ZookeeperLeaderElector(controllerContext, ZkUtils.ControllerPath, onControllerFailover,
    onControllerResignation, config.brokerId)
这边的这个ZkUtils.ControllerPath是一个String,它的值是
val ControllerPath = "/controller"

这个路径是改broker尝试要在znode中申请的节点路径

onControllerResignation是leader选举成功后的回调函数

现在我们跟着最后一行,来到了kafka.server下的ZookeeperLeaderElector类,这个类初始化时做了两个动作,electionPath就是刚才提到的ControllerPath

def startup {
  inLock(controllerContext.controllerLock) {
    controllerContext.zkUtils.zkClient.subscribeDataChanges(electionPath, leaderChangeListener)
    elect
  }
}
看到初始化ZookeeperLeaderElector时进行了两个动作:

1.注册electionPath改变之后的回调函数,选举出新leader或者leader改变,崩溃都会造成这个函数被调用

2.进行选举

我直接扒出了选举的核心代码.可以看到kafka选主的方式就是简单粗暴的创建Emphemeral节点,谁能够创建成功谁就是leader.重要的znode的路径包含在electionPath中,而这个electionPath是所有的kafka约定好的,而且是hard code在代码里边的,就是上文提到的ControllerPath

try {
  val zkCheckedEphemeral = new ZKCheckedEphemeral(electionPath,
                                                  electString,
                                                  controllerContext.zkUtils.zkConnection.getZookeeper,
                                                  JaasUtils.isZkSecurityEnabled())
  //ZKCheckedEphemeral一个临时节点,创建这个节点的成员一旦退出zk,这个临时节点也会消失.
  //如果创建时发现一个同名的znode路径就会报ZkNodeExistsException错误
  zkCheckedEphemeral.create()
  info(brokerId + " successfully elected as leader")
  leaderId = brokerId
  //这个回调只有当选举成功后才会执行
  onBecomingLeader()
} catch {
  case e: ZkNodeExistsException =>
    // If someone else has written the path, then
    leaderId = getControllerID 

    if (leaderId != -1)
      debug("Broker %d was elected as leader instead of broker %d".format(leaderId, brokerId))
    else
      warn("A leader has been elected but just resigned, this will result in another round of election")

  case e2: Throwable =>
    error("Error while electing or becoming leader on broker %d".format(brokerId), e2)
    resign()
}
Emphemeral Node可以说是选举的灵魂所在,选举只需简单地调用这个create()函数,程序会尝试在zk中以指定路径名创建一个znode.成功了程序会顺利执行下去,而一旦有路径名相同的znode就会抛出exception

在我们了解了kafka的leader是如何选举之后.我们来看一下,选举成为leader后的回调,这个onBecomingLeader()函数

def onControllerFailover() {
  if(isRunning) {
    info("Broker %d starting become controller state transition".format(config.brokerId))
    //read controller epoch from zk
    readControllerEpochFromZookeeper()
    // increment the controller epoch
    incrementControllerEpoch(zkUtils.zkClient)
    // before reading source of truth from zookeeper, register the listeners to get broker/topic callbacks
    registerReassignedPartitionsListener()  //"/admin/reassign_partitions"
    registerIsrChangeNotificationListener() //"/isr_change_notification"
    registerPreferredReplicaElectionListener() //"/admin/preferred_replica_election"
    partitionStateMachine.registerListeners()  //"/brokers/topics"  同时视用户的config决定是否要注册"/admin/delete_topics"
    replicaStateMachine.registerListeners()    //"/brokers/ids" 重点,监视所有的follower的加入,离开集群的行为
我只截取了其中比较重要的部分,同时把每一句所注册的监视路径标注在后边了.

我们看到一开始leader会自增一个Epoch. 在kafka中epoch这个概念和raft协议中的term很相似,简单来说,epoch通常是一个long变量,是一个leader拥有统治权利的象征.就像康熙年间,乾隆年间,在这两个皇帝的统治下他们的epoch就是"康熙"和"乾隆",每一份指令都会表上自己所在的epoch来代表权威.同时你要是在乾隆年间拿着一份康熙的指令,那么人们(follower)就会知道这份指令过时了,从而拒绝这份指令.


看到这里,我们也能大致想到kafka集群leader与follower之间的分工.

follower只负责监视leader有没有改变,只会注册对electionPath的监视.

leader要监视的内容就比较多了,他会监视上面一段代码中的那些路径.所有的broker加入集群都会在/broker/ids下注册自己的brokerId,leader通过/broker/ids这个路径就可以知道集群中follower的变化情况.

要是leader自己崩了呢?没关系,follower监视了leader的情况,一旦leader崩了,follower就会选举出一个新的leader.


这么做有什么好处呢?最明显的就是降低了zookeeper的负载,要是所有的broker都监视/brokers/ids那么一旦集群成员发生变化,那么zk得挨个提醒broker回调.但是现在只用提醒leader就行了,唯一让zk很辛苦的情况就是leader崩溃.不过这种情况比较少,难得辛苦还是可以接收的!


你可能感兴趣的:(kafka)