上一篇文章说到,当创建一个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崩溃.不过这种情况比较少,难得辛苦还是可以接收的!