KafkaController
说明,这个实例主要用于对kafka cluster进行管理,一个kafka的cluster表示同一个zk环境下所有的broker的集合,在这个cluster中需要有一个broker被选举成为leader,用于管理其它的broker的上线与下线的处理,对partition与副本的分配,管理topic的添加/修改/删除操作。
实例创建与启动
/* start kafka controller */
kafkaController = new KafkaController(config, zkUtils, brokerState,
kafkaMetricsTime, metrics, threadNamePrefix)
kafkaController.startup()
实例初始化:
this.logIdent = "[Controller " + config.brokerId + "]: "
private var isRunning = true
private val stateChangeLogger = KafkaController.stateChangeLogger
val controllerContext = new ControllerContext(zkUtils, config.zkSessionTimeoutMs)
用于对partition的状态进行维护与更新操作.
val partitionStateMachine = new PartitionStateMachine(this)
用于对每个partition的每一个副本的状态进行维护与更新操作.
val replicaStateMachine = new ReplicaStateMachine(this)
生成用于监听leader发生变化的zookeeper的监听,用于当前的broker正在成为leader或者broker正在失去leader.
private val controllerElector = new ZookeeperLeaderElector(controllerContext,
ZkUtils.ControllerPath, onControllerFailover,
onControllerResignation, config.brokerId)
private val autoRebalanceScheduler = new KafkaScheduler(1)
var deleteTopicManager: TopicDeletionManager = null
下面生成用于对partition的leader的选择的对应的选择器,包含partition加载时,重新分配时,首先副本,controll下线
val offlinePartitionSelector = new
OfflinePartitionLeaderSelector(controllerContext, config)
private val reassignedPartitionLeaderSelector = new
ReassignedPartitionLeaderSelector(controllerContext)
private val preferredReplicaPartitionLeaderSelector = new
PreferredReplicaPartitionLeaderSelector(controllerContext)
private val controlledShutdownPartitionLeaderSelector = new
ControlledShutdownLeaderSelector(controllerContext)
private val brokerRequestBatch = new ControllerBrokerRequestBatch(this)
这里生成几个用于监听partition的修改与isr的修改的监听器.
private val partitionReassignedListener = new PartitionsReassignedListener(this)
这个用于在对partition的首先副本进行显示重新修改时,
用于监听对/admin/preferred_replica_election路径的修改.
private val preferredReplicaElectionListener = new
PreferredReplicaElectionListener(this)
用于监听当partition的leader发生变化时,更新partitionLeadershipInfo集合的内容,同时向所有的brokers节点发送metadata修改的请求.
private val isrChangeNotificationListener = new
IsrChangeNotificationListener(this)
对KafkaController的实例启动:
def startup() = {
inLock(controllerContext.controllerLock) {
info("Controller starting up")
这里有于监听对ZK的session的注册,当向zk进行注册时,生成一个session后会调用onControllerResignation函数.这个函数会先取消对zookeeper的监听(partition相关),停止掉对应的管理组合.
registerSessionExpirationListener()
isRunning = true
在/controller的zk路径上注册监听LeaderChangeListener实例.并执行选举.
controllerElector.startup
info("Controller startup complete")
}
}
leader的选举与动作
启动时的选举
通过在controller启动时调用ZookeeperLeaderElector实例中的startup函数.
这个函数在/controller中注册一个LeaderChangeListener的监听器,用于监听controller中的内容改变与删除.并执行函数进行elect选举.
def elect: Boolean = {
val timestamp = SystemTime.milliseconds.toString
val electString = Json.encode(Map("version" -> 1,
"brokerid" -> brokerId, "timestamp" -> timestamp))
这里直接从zookeeper中找到/controller路径下的内容,如果路径下存在内容时,直接读取这个节点的内容,并解析出leaderid(也就是被选为leader的broker),如果leader已经被选举出来,直接返回.
leaderId = getControllerID
if(leaderId != -1) {
debug("Broker %d has been elected as leader, so stopping the election
process.".format(leaderId))
return amILeader
}
流程执行到这里,表示当前还没有broker节点被选举为leader.
try {
这里尝试把当前节点注册成kafka的leader,通过create函数,如果这个函数执行成功,没有返回异常时,表示当前节点被选举成了leader,把当前的brokerId设置到leaderId的属性上.
val zkCheckedEphemeral = new ZKCheckedEphemeral(electionPath,
electString,
controllerContext.zkUtils.zkConnection.getZookeeper,
JaasUtils.isZkSecurityEnabled())
zkCheckedEphemeral.create()
info(brokerId + " successfully elected as leader")
leaderId = brokerId
这里执行生成此实例传入的函数来执行leader的进入,这个函数的实现为KafkaController中onControllerFailover的函数.
onBecomingLeader()
} catch {
case e: ZkNodeExistsException =>
如果通过ZKCheckedEphemeral去在zk中创建内容时失败,返回的错误是节点已经存在时,表示在这个kafka的server注册前,已经被其它的节点注册成了leader,读取/controller的路径,并设置其内容中的brokerId为leaderId.
// 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 =>
如果是其它的错误,设置leaderId的值为-1,同时删除/controller的路径的内容.
error("Error while electing or becoming leader on broker %d"
.format(brokerId), e2)
resign()
}
这里返回的值如果是true表示当前节点就是leader,否则表示当前节点不是leader,对应的leaderId值就是leader节点的brokerId.
amILeader
}
Broker被选举成leader后controller的处理
当当前的kafka server被选举成为leader后,会执行KafkaController中的onControllerFailover函数来处理leader的进入.
def onControllerFailover() {
if(isRunning) {
info("Broker %d starting become controller state
transition".format(config.brokerId))
//read controller epoch from zk
从/controller_epoch路径上读取controller的epoch的值,这个值用于控制controller的切换.
把读取到的值存储到controllerContext中的epoch与epochZkVersion属性中.
readControllerEpochFromZookeeper()
更新controllerContext中的epoch的值(加1),并持久化到/controller_epoch路径上.
// increment the controller epoch
incrementControllerEpoch(zkUtils.zkClient)
注册对/admin/reassign_partitions节点的监听处理程序,由PartitionsReassignedListener实现.用于监听partition的重新分配的动作.主要用于监听节点的内容修改
registerReassignedPartitionsListener()
注册对/isr_change_notification节点的监听处理程序,这个节点主要用于通知partitoin的isr的变化,由IsrChangeNotificationListener实现.主要用于监听节点的内容修改,
registerIsrChangeNotificationListener()
注册对/admin/preferred_replica_election节点的监听处理程序,这个节点用于对副本的首选节点进行处理,由PreferredReplicaElectionListener实现.主要用于监听节点的内容修改.
registerPreferredReplicaElectionListener()
在partitionStateMachine中对/brokers/topics节点注册监听处理程序,用于监听topic的修改,由TopicChangeListener实现.主要用于监听这个节点的子节点的修改.
如果配置有deletetopic的启用时,通过配置delete.topic.enable,默认为false.如果这个值配置为true时,对/admin/delete_topics节点注册一个DeleteTopicsListener监听处理程序,用于监听这个节点下的子节点的修改.
partitionStateMachine.registerListeners()
对/brokers/ids节点注册一个BrokerChangeListener监听处理程序,用于监听这个节点的子节点的修改,主要用于监听broker的的改变.
replicaStateMachine.registerListeners()
初始化controller的上下文.
initializeControllerContext()
启动对broker状态的监听与partition的状态监听的实例.
replicaStateMachine.startup()
partitionStateMachine.startup()
根据现在kafka中所有的topic,对/brokers/topics/topicname节点注册一个AddPartitionsListener监听处理程序,用于监听这个topic的修改.
// register the partition change listeners for all existing topics on failover
controllerContext.allTopics.foreach(topic =>
partitionStateMachine.registerPartitionChangeListener(topic))
info("Broker %d is ready to serve as the new controller with epoch
%d".format(config.brokerId, epoch))
设置当前的broker的状态为RunningASController的broker,表示这是一个leader的节点.
brokerState.newState(RunningAsController)
这里对未完成partition的副本重新分配的partitionsBeingReassigned集合进行迭代,执行如下的流程处理:
1,根据准备重新分配的partition的副本所在的节点集合,检查当前liveBrokers中是否都存在这些节点,如果要重新分配的节点集合中有在liveBrokers中不包含的节点,表示要分配的副本所在节点有没有启动的节点,throw exception,
2,根据需要重新分配的partition从partitionReplicaAssignment集合中找到对应的partition的信息,这个集合中存储了已经分配的partition的副本信息,如果在已经分配的partition的集合中找不到这个partition,throw exception.
3,如果准备重新分配的副本节点集合与现在partitionReplicaAssignment集合中parition对应的副本节点集合是相同的内容,表示重新分配是没有必要的,throw exception.
4,这种情况表示重新分配的副本节点集合对应的节点都已经启动,同时这个集合与现在此partition对应的分配副本节点集合不相同,执行如下的子流程:
4,1,在/brokers/topics/tpname/partitions/pid/state路径上生成注册一个用于监听isr的变化的ReassignedPartitionsIsrChangeListener监听程序.
4,2,在deleteTopicManager中检查这个topic是否是需要删除的topic,如果是,添加到准备删除的topic的集合中.
4,3,执行对partition中副本的重新分配,通过onPartitionReassignment函数.
maybeTriggerPartitionReassignment()
这里对还未完成首选副本分配的partition进行首选副本分配的操作,这些未分配首选副本存储在partitionsUndergoingPreferredReplicaElection集合中.
1,首先检查对应的partitions的topic是否是已经被删除的topic,如果包含有要删除的topic时,把对应的partitions集合添加到待删除的topic partitions的集合中.
2,通过partitionStateMachine实例修改要进行首选副本分配的所有的partitions的状态为OnlinePartition.并通过preferredReplicaPartitionLeaderSelector实例进行partition的首选副本的选择操作,通过读取/brokers/topics/topicname/partitions/pid/state路径的isr的信息,如果这个路径还不存在时,根据当前partition的所有活着的副本集合,取第一个副本为leader,并把这个副本集合存储到这个路径中,根据读取这个路径的信息,通过leaderSelector来进行首选副本的分配.
3,更新partitionLeadershipInfo集合的内容,把这个partition对应的isr存储到这个集合中,并向对应的broker节点发送LeaderAndIsrRequest请求.
4,移出partitionsUndergoingPreferredReplicaElection集合中的内容,
并删除/admin/preferred_replica_election节点的数据.
maybeTriggerPreferredReplicaElection()
向所有的bro