前面已经分析过kafka server的启动过程,以及server所能处理的所有的request,即KafkaApis
剩下的,其实关键就是controller,以及partition和replica的状态机
这里先看看controller在broker server的基础上,多做了哪些初始化和failover的工作

 

最关键的一句,

private val controllerElector = new ZookeeperLeaderElector(controllerContext, ZkUtils.ControllerPath, onControllerFailover,
onControllerResignation, config.brokerId)

 

kafka.server.ZookeeperLeaderElector

参数的含义比较明显,注意两个callback,是关键
class ZookeeperLeaderElector(controllerContext: ControllerContext,
electionPath: String,
onBecomingLeader: () => Unit,
onResigningAsLeader: () => Unit,
brokerId: Int)

做两件事,

1. 建立watcher,去listen “election path” in ZK, "/controller";

当controller发生变化是,可以做相应的处理

controllerContext.zkClient.subscribeDataChanges(electionPath, leaderChangeListener)

LeaderChangeListener

监听这个path的两个行为,
handleDataChange,即controller发生了变化,这个比较简单,只需要把local的leaderid更新一下就好
leaderId = KafkaController.parseControllerId(data.toString)
handleDataDeleted,这种情况即,controller被删除了,一般是挂了
挂了就重新做elect,但是多个判断,如果本来我就是leader,那先调用onResigningAsLeader,即onControllerResignation,做些清理的工作,比如deregister watcher,关闭各种状态机
if(amILeader)
onResigningAsLeader()
elect

 

2. elect, 试图去创建EphemeralPath,从而成为Controller

用createEphemeralPathExpectConflictHandleZKBug,试图去创建EphemeralNode,

如果不成功说明已经被别人抢占

成功就说明我成为了controller,调用onBecomingLeader(),即onControllerFailover

 

KafkaController.onControllerFailover

这个callback就是controller初始化的关键,

/**
* This callback is invoked by the zookeeper leader elector on electing the current broker as the new controller.
* It does the following things on the become-controller state change -
* 1. Register controller epoch changed listener
* 2. Increments the controller epoch
* 3. Initializes the controller's context object that holds cache objects for current topics, live brokers and
* leaders for all existing partitions.
* 4. Starts the controller's channel manager
* 5. Starts the replica state machine
* 6. Starts the partition state machine
* If it encounters any unexpected exception/error while becoming controller, it resigns as the current controller.
* This ensures another controller election will be triggered and there will always be an actively serving controller
*/
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(zkClient) //增加ControllerEpoch,以区分过期
// before reading source of truth from zookeeper, register the listeners to get broker/topic callbacks
registerReassignedPartitionsListener()
registerPreferredReplicaElectionListener()
partitionStateMachine.registerListeners()
replicaStateMachine.registerListeners()
initializeControllerContext() //从zk读出broker,topic的信息以初始化controllerContext
replicaStateMachine.startup() //启动状态机
partitionStateMachine.startup()
// register the partition change listeners for all existing topics on failover
controllerContext.allTopics.foreach(topic => partitionStateMachine.registerPartitionChangeListener(topic)) //为每个topic增加partition变化的watcher
info("Broker %d is ready to serve as the new controller with epoch %d".format(config.brokerId, epoch)) //完成日志
brokerState.newState(RunningAsController)
maybeTriggerPartitionReassignment()
maybeTriggerPreferredReplicaElection()
/* send partition leadership info to all live brokers */
sendUpdateMetadataRequest(controllerContext.liveOrShuttingDownBrokerIds.toSeq) //如注释说,用于成为controller,需要把partition leadership信息发到所有brokers,大家要以我为准
if (config.autoLeaderRebalanceEnable) { //如果打开outoLeaderRebalance,需要把partiton leader由于dead而发生迁徙的,重新迁徙回去
info("starting the partition rebalance scheduler")
autoRebalanceScheduler.startup()
autoRebalanceScheduler.schedule("partition-rebalance-thread", checkAndTriggerPartitionRebalance,
5, config.leaderImbalanceCheckIntervalSeconds, TimeUnit.SECONDS)
}
deleteTopicManager.start()
}
else
info("Controller has been shut down, aborting startup/failover")
}

 

这里最后再讨论一下createEphemeralPathExpectConflictHandleZKBug

这个函数看着比较诡异,handleZKBug,到底是zk的什么bug?

注释给出了,https://issues.apache.org/jira/browse/ZOOKEEPER-1740

这个问题在于“The current behavior of zookeeper for ephemeral nodes is that session expiration and ephemeral node deletion is not an atomic operation.”

即zk的session过期和ephemeral node删除并不是一个原子操作,所以带来的问题的过程如下,

1. client session超时,它会假设在zk,这个ephemeral node已经被删除,但是zk当前由于某种原因hang住了,比如very long fsync operations,所以其实这个ephemeral node并没有被删除

2. 但client是认为ephemeral node已经被删除,所以它会尝试重新创建这个ephemeral node,但得到的结果是NodeExists,因为这个node并没有被删除,那么client想既然有就算了

3. 这个时候,zk从very long fsync operations的hang中恢复,它会继续之前没有完成的操作,把ephemeral node删掉

4. 这样client就会发现ephemeral node不存在了,虽然session并没有超时

所以这个函数就是为了规避这个问题,

方法就是,当我发现NodeExists时,说明zk当前hang住了,这个时候我需要等待,并反复尝试,直到zk把这个node删除后,我再重新创建

def createEphemeralPathExpectConflictHandleZKBug(zkClient: ZkClient, path: String, data: String, expectedCallerData: Any, checker: (String, Any) => Boolean, backoffTime: Int): Unit = {
while (true) {
try {
createEphemeralPathExpectConflict(zkClient, path, data)
return
} catch {
case e: ZkNodeExistsException => {
// An ephemeral node may still exist even after its corresponding session has expired
// due to a Zookeeper bug, in this case we need to retry writing until the previous node is deleted
// and hence the write succeeds without ZkNodeExistsException
ZkUtils.readDataMaybeNull(zkClient, path)._1 match {
case Some(writtenData) => {
if (checker(writtenData, expectedCallerData)) {
info("I wrote this conflicted ephemeral node [%s] at %s a while back in a different session, ".format(data, path)
+ "hence I will backoff for this node to be deleted by Zookeeper and retry") Thread.sleep(backoffTime)
} else {
throw e
}
}
case None => // the node disappeared; retry creating the ephemeral node immediately
}
}
case e2: Throwable => throw e2
}
}
}

这个方式有个问题就是,如果zk hang住,这里的逻辑是while true,这个函数也会一直hang住

Apache Kafka源码分析 - kafka controller的更多相关文章

  1. Apache Kafka源码分析 – Broker Server

    1. Kafka.scala 在Kafka的main入口中startup KafkaServerStartable, 而KafkaServerStartable这是对KafkaServer的封装 1: ...

  2. Kafka源码分析(一) - 概述

    系列文章目录 https://zhuanlan.zhihu.com/p/367683572 目录 系列文章目录 一. 实际问题 二. 什么是Kafka, 如何解决这些问题的 三. 基本原理 1. 基本 ...

  3. Kafka源码分析系列-目录(收藏不迷路)

    持续更新中,敬请关注! 目录 <Kafka源码分析>系列文章计划按"数据传递"的顺序写作,即:先分析生产者,其次分析Server端的数据处理,然后分析消费者,最后再补充 ...

  4. Kafka源码分析(三) - Server端 - 消息存储

    系列文章目录 https://zhuanlan.zhihu.com/p/367683572 目录 系列文章目录 一. 业务模型 1.1 概念梳理 1.2 文件分析 1.2.1 数据目录 1.2.2 . ...

  5. k8s client-go源码分析 informer源码分析(5)-Controller&Processor源码分析

    client-go之Controller&Processor源码分析 1.controller与Processor概述 Controller Controller从DeltaFIFO中pop ...

  6. apache kafka源码分析-Producer分析---转载

    原文地址:http://www.aboutyun.com/thread-9938-1-1.html 问题导读1.Kafka提供了Producer类作为java producer的api,此类有几种发送 ...

  7. kafka源码分析之一server启动分析

    0. 关键概念 关键概念 Concepts Function Topic 用于划分Message的逻辑概念,一个Topic可以分布在多个Broker上. Partition 是Kafka中横向扩展和一 ...

  8. Kafka源码分析及图解原理之Producer端

    一.前言 任何消息队列都是万变不离其宗都是3部分,消息生产者(Producer).消息消费者(Consumer)和服务载体(在Kafka中用Broker指代).那么本篇主要讲解Producer端,会有 ...

  9. Kafka源码分析(二) - 生产者

    系列文章目录 https://zhuanlan.zhihu.com/p/367683572 目录 系列文章目录 一. 使用方式 step 1: 设置必要参数 step 2: 创建KafkaProduc ...

随机推荐

  1. 小甲鱼PE详解之基址重定位详解(PE详解10)

    今天有一个朋友发短消息问我说“老师,为什么PE的格式要讲的这么这么细,这可不是一般的系哦”.其实之所以将PE结构放在解密系列继基础篇之后讲并且尽可能细致的讲,不是因为小甲鱼没事找事做,主要原因是因为P ...

  2. js:数据结构笔记10--图和图算法

    图:是由边和定点的集合组成:  按照图的定点对是否有序可以分为:有向图和无向图:  路径:所有顶点都由边连接构成:路径长度为第一个定点到最后一个顶点之间的数量:  环:指向自身的顶点,长度为0:圈:至 ...

  3. iOS Simulator功能介绍关于Xamarin IOS开发

    iOS Simulator功能介绍关于Xamarin IOS开发 iOS Simulator功能介绍 在图1.38所示的运行效果中,所见到的类似于手机的模型就是iOS Simulator.在没有iPh ...

  4. POJ3057 Evacuation(二分图最大匹配)

    人作X部:把门按时间拆点,作Y部:如果某人能在某个时间到达某门则连边.就是个二分图最大匹配. 时间可以二分枚举,或者直接从1枚举时间然后加新边在原来的基础上进行增广. 谨记:时间是个不可忽视的维度. ...

  5. BZOJ4311 : 向量

    考虑离线操作,求出每个向量存在的时间区间,用时间线段树来进行分治,在每个节点求出凸壳后,询问时在凸壳上三分答案.时间复杂度$O(n\log^2n)$. #include<cstdio> # ...

  6. HDU 4511 (AC自动机+状态压缩DP)

    题目链接:  http://acm.hdu.edu.cn/showproblem.php?pid=4511 题目大意:从1走到N,中间可以选择性经过某些点,比如1->N,或1->2-> ...

  7. startInstrumentation asks to run as user -2 but is calling from user 0; this requires android.permission.INTERACT_ACROSS_USERS_FULL

    由于手头上一直没有android level 17及以上版本的手机,有一个shell命令启动脚本的BUG,发生在SDK level 17及以上 API>=17中加入了INTERACT_ACROS ...

  8. robotium 新建 android 测试项目:

    注意:新建项目后再运行前一定要修改Manifest文件中的instrumentation 中的target package, 这个是测试的入口 1. 程序开始要通知系统我要测的app是什么 如何知道a ...

  9. COJ975 WZJ的数据结构(负二十五)

    试题描述 输入一个字符串S,回答Q次问题,给你l,r,输出子序列[l,r]的最长连续回文串长度. 输入 第一行为一个字符串S. 第二行为一个正整数Q. 接下来Q行每行为l,r. 输出 对于每个询问,输 ...

  10. 泛型,动态创建List<T> (转摘)

    第一种: static void Main()        {            object intList = MakeList(typeof(int), 1, 2, 3);         ...