在KafkaServer启动过程的入口中,会启动Replica Manager,众所周知,这是一个副本管理器。replica在Kafka中扮演的角色很重要,是保证消息不丢失的一个重要概念。

replica的个人理解概念如下:producer发送的消息给broker,broker是分为多个partition的,对于同一个partition中的broker,这些机器是有主从的概念的。producer只会向leader写入消息,consumer只会从leader读取消息,(leader负责读写,replica保证消息不丢)。为了保证消息不丢失,follower会定时从leader拉取消息,保持与leader的消息同步。当然,producer可以配置是否需要有follower同步成功,以及需要多少个replica,(即需要多少个ack)才算是消息发送成功。这块看个人的需求。

下面我们看下Replica Manager的启动过程。

一、入口

入口在KafkaServer的start方法中,比较简洁:

  1. replicaManager = new ReplicaManager(config, metrics, time, kafkaMetricsTime, zkUtils, kafkaScheduler, logManager,isShuttingDown)
  2. replicaManager.startup()

我们主要看下ReplicaManager里面都有什么内容。

二、ReplicaManager实例化

我们看看实例化的过程:

  1. /* epoch of the controller that last changed the leader */
  2. @volatile var controllerEpoch: Int = KafkaController.InitialControllerEpoch - 1
  3. private val localBrokerId = config.brokerId
  4. private val allPartitions = new Pool[(String, Int), Partition](valueFactory = Some { case (t, p) =>
  5. new Partition(t, p, time, this)
  6. })
  7. private val replicaStateChangeLock = new Object
  8. val replicaFetcherManager = new ReplicaFetcherManager(config, this, metrics, jTime, threadNamePrefix)
  9. private val highWatermarkCheckPointThreadStarted = new AtomicBoolean(false)
  10. val highWatermarkCheckpoints = config.logDirs.map(dir => (new File(dir).getAbsolutePath, new OffsetCheckpoint(new File(dir, ReplicaManager.HighWatermarkFilename)))).toMap
  11. private var hwThreadInitialized = false
  12. this.logIdent = "[Replica Manager on Broker " + localBrokerId + "]: "
  13. val stateChangeLogger = KafkaController.stateChangeLogger
  14. private val isrChangeSet: mutable.Set[TopicAndPartition] = new mutable.HashSet[TopicAndPartition]()
  15. private val lastIsrChangeMs = new AtomicLong(System.currentTimeMillis())
  16. private val lastIsrPropagationMs = new AtomicLong(System.currentTimeMillis())
  17. val delayedProducePurgatory = DelayedOperationPurgatory[DelayedProduce](
  18. purgatoryName = "Produce", config.brokerId, config.producerPurgatoryPurgeIntervalRequests)
  19. val delayedFetchPurgatory = DelayedOperationPurgatory[DelayedFetch](
  20. purgatoryName = "Fetch", config.brokerId, config.fetchPurgatoryPurgeIntervalRequests)
  • 首先是这个controllerEpoch,这个值表示的是leader发生变化时controller的epoch。epoch存储在zk中的/Controller_epoch中。
  • 第二步是从配置broker.id中获取当前机器的brokerId。
  • 实例化ReplicaFetcherManager,是一个follower从leader拉取消息的管理器,这里面有文章。
  • 设置highWatermarkCheckPointThreadStarted为false,为了后续启动相关的线程用。
  • 从文件(replication-offset-checkpoint)中获取所有topic和partition的HW,这个文件中存储了每个topic和partition对应的最新的checkPoint对应的offset值。HW表示的是topic的partition对应的最后一次commit的消息的offset值,也是用于消息完整性的保证。
  • 定义了isrChangerSet,表示了isr改变顺序的集合。至于isr是干啥的,网上的内容比较多,搜索即可。
  • 最后涉及到两个配置,分别是:
    • producer.purgatory.purge.interval.requests:默认值1000,用于在procucer的ack设置是-1或者1时,跟踪消息是否添加成功,使用DelayedProduce实现。成功后清除。
    • fetch.purgatory.purge.interval.requests:默认值1000,fetch 请求清除时的清除间隔

三、启动ReplicaManager

我们主要看下ReplicaManager的start方法:

  1. def startup() {
  2. // start ISR expiration thread
  3. scheduler.schedule("isr-expiration", maybeShrinkIsr, period = config.replicaLagTimeMaxMs, unit = TimeUnit.MILLISECONDS)
  4. scheduler.schedule("isr-change-propagation", maybePropagateIsrChanges, period = 2500L, unit = TimeUnit.MILLISECONDS)
  5. }

这块主要启动了两个定时任务,分别是maybeShrinkIsr和maybePropagateIsrChanges。下面我们着重分析下。

3.1 maybeShrinkIsr

这个方法的调用时间间隔由配置replica.lag.time.max.ms控制,主要用于检查partition对应的isr列表中是否有心跳过期的isr。

  1. private def maybeShrinkIsr(): Unit = {
  2. trace("Evaluating ISR list of partitions to see which replicas can be removed from the ISR")
  3. allPartitions.values.foreach(partition => partition.maybeShrinkIsr(config.replicaLagTimeMaxMs))
  4. }

这块主要是遍历了所有的partition,每个partition都执行maybeShrinkIsr方法,下面我们进入maybeShrinkIsr,分析下主要做了哪些事情。

  1. def maybeShrinkIsr(replicaMaxLagTimeMs: Long) {
  2. val leaderHWIncremented = inWriteLock(leaderIsrUpdateLock) {
  3. leaderReplicaIfLocal() match {
  4. case Some(leaderReplica) =>
  5. val outOfSyncReplicas = getOutOfSyncReplicas(leaderReplica, replicaMaxLagTimeMs)
  6. if(outOfSyncReplicas.size > 0) {
  7. val newInSyncReplicas = inSyncReplicas -- outOfSyncReplicas
  8. assert(newInSyncReplicas.size > 0)
  9. info("Shrinking ISR for partition [%s,%d] from %s to %s".format(topic, partitionId,
  10. inSyncReplicas.map(_.brokerId).mkString(","), newInSyncReplicas.map(_.brokerId).mkString(",")))
  11. // update ISR in zk and in cache
  12. updateIsr(newInSyncReplicas)
  13. // we may need to increment high watermark since ISR could be down to 1
  14. replicaManager.isrShrinkRate.mark()
  15. maybeIncrementLeaderHW(leaderReplica)
  16. } else {
  17. false
  18. }
  19. case None => false // do nothing if no longer leader
  20. }
  21. }

整个步骤如下:

  • leaderReplicaIfLocal:先检查当前的partition的leader是否为当前的broker,如果为是,就不进入下面的方法,否则进入下面的方法。
  • getOutOfSyncReplicas:获取不同步的replica列表,获取的方法是首先从isr中去除掉leader,然后把当前时间-lastCaughtUpTimeMs大于replicaMaxLagTimeMs的replica筛选出来,即为outOfSyncReplicas。这里面的lastCaughtUpTimeMs是指上次同步的时间,不一定是心跳时间。
  • 如果outOfSyncReplicas中存在replica,则继续。两个列表进行差值运算后得到新的isr列表,之后更新isr列表(即zk中的数据)。
  • 最后可能需要更新下HW

3.2 maybePropagateIsrChanges

这个方法的调用时间是固定的,不由配置决定,代码中写死,为2500ms。这个方法会把isr的变化内容更新到zk中去,执行这个方法的条件是:

  • ISR变化没有被广播出去
  • 最近5s内没有ISR变化或者上次广播的时间距离当前时间超过了60s,其实这里的广播就是指写入到zk中
  1. def maybePropagateIsrChanges() {
  2. val now = System.currentTimeMillis()
  3. isrChangeSet synchronized {
  4. if (isrChangeSet.nonEmpty &&
  5. (lastIsrChangeMs.get() + ReplicaManager.IsrChangePropagationBlackOut < now ||
  6. lastIsrPropagationMs.get() + ReplicaManager.IsrChangePropagationInterval < now)) {
  7. ReplicationUtils.propagateIsrChanges(zkUtils, isrChangeSet)
  8. isrChangeSet.clear()
  9. lastIsrPropagationMs.set(now)
  10. }
  11. }
  12. }

【Kafka源码】ReplicaManager启动过程的更多相关文章

  1. Symfony2源码分析——启动过程2

    文章地址:http://www.hcoding.com/?p=46 上一篇分析Symfony2框架源码,探究Symfony2如何完成一个请求的前半部分,前半部分可以理解为Symfony2框架为处理请求 ...

  2. quartz2.x源码分析——启动过程

    title: quartz2.x源码分析--启动过程 date: 2017-04-13 14:59:01 categories: quartz tags: [quartz, 源码分析] --- 先简单 ...

  3. mysql源码分析-启动过程

    mysql源码分析-启动过程 概要 # sql/mysqld.cc, 不包含psi的初始化过程 mysqld_main: // 加载my.cnf和my.cnf.d,还有命令行参数 if (load_d ...

  4. Nginx学习笔记(六) 源码分析&启动过程

    Nginx的启动过程 主要介绍Nginx的启动过程,可以在/core/nginx.c中找到Nginx的主函数main(),那么就从这里开始分析Nginx的启动过程. 涉及到的基本函数 源码: /* * ...

  5. Symfony2源码分析——启动过程1

    本文通过阅读分析Symfony2的源码,了解Symfony2启动过程中完成哪些工作,从阅读源码了解Symfony2框架. Symfony2的核心本质是把Request转换成Response的一个过程. ...

  6. 分布式事务_02_2PC框架raincat源码解析-启动过程

    一.前言 上一节已经将raincat demo工程运行起来了,这一节来分析下raincat启动过程的源码 主要包括: 事务协调者启动过程 事务参与者启动过程 二.协调者启动过程 主要就是在启动类中通过 ...

  7. Spring MVC源码(一) ----- 启动过程与组件初始化

    SpringMVC作为MVC框架近年来被广泛地使用,其与Mybatis和Spring的组合,也成为许多公司开发web的套装.SpringMVC继承了Spring的优点,对业务代码的非侵入性,配置的便捷 ...

  8. Spring Boot源码分析-启动过程

    Spring Boot作为目前最流行的Java开发框架,秉承"约定优于配置"原则,大大简化了Spring MVC繁琐的XML文件配置,基本实现零配置启动项目. 本文基于Spring ...

  9. Syncthing源码解析 - 启动过程

    我相信很多朋友会认为启动就是双击一下Syncthing程序图标,随后就启动完毕了!如果这样认为,对,也不对!对,是因为的确是这样操作,启动了Syncthing:不对是因为在调试Syncthing启动过 ...

  10. Redis源码研究--启动过程

    ---------------------6月23日--------------------------- Redis启动入口即main函数在redis.c文件,伪代码如下: int main(int ...

随机推荐

  1. win32多线程编程

    关于多线程多进程的学习,有没有好的书籍我接触的书里头关于多线程多进程部分,一是<操作系统原理>里面讲的相关概念   一个是<linux基础教程>里面讲的很简单的多线程多进程编程 ...

  2. LPCTSTR LPCWSTR LPCSTR 含义

    #ifdef UNICODE#define LPCTSTR LPCWSTR#else#define LPCTSTR LPCSTR#endif      LPCTSTR A 32-bit pointer ...

  3. Windows下 如何添加开机启动项

    Windows XP,Windows 7: 开始 ----> 所有程序 ----> 启动, 右键打开"启动"这个文件夹, 把想开机自动启动的软件快捷方式拖进去即可. ( ...

  4. php中常用的字符串大小写转换函数实例解释

    PHP字符串处理函数中,最为简单的几个函数,相关解释就不上了,直接看例子. PHP字符串处理函数中,最为简单的几个函数,相关解释就不上了,直接看例子. strtolower函数.strtoupper函 ...

  5. PBOC圈存时用到3DES加密解密以及MAC计算方法

    最近在做PBOC圈存时用到了3DES的加密解密以及MAC计算问题,在网上了解一些知识,复制了一些demo学习,我这里没有深入研究,只是把我用到的和了解的做个总结,便于以后使用和学习. 3DES分双倍长 ...

  6. Java面向对象 String 基本数据类型对象包装类

      Java面向对象  String 知识概要:              (1)String的用法详解 (2)基本数据类型对象包装类 String          顾名思义,该类主要是对字符串 ...

  7. asp.net或javascript判断是否手机访问

    /// <summary> /// 判断手机用户UserAgent /// </summary> /// <returns></returns> pri ...

  8. GPUImage库的使用

    GPUImage开源项目地址:https://github.com/BradLarson/GPUImage GPUImage使用说明:https://github.com/BradLarson/GPU ...

  9. (转)Python-正则表达式

    在前面我们已经搞定了怎样获取页面的内容,不过还差一步,这么多杂乱的代码夹杂文字我们怎样把它提取出来整理呢?下面就开始介绍一个十分强大的工具,正则表达式! 1.了解正则表达式 正则表达式是对字符串操作的 ...

  10. ABAP POH和POV事件中 获得屏幕字段的值

    在Screen显示之前,系统会自动将程序变量值放到屏幕字段中:在PAI事件中,系统会自动将屏幕字段的值更新到相应的程序变量. 在Screen Logic中我们还有POH和POV事件,所以有时需要调用函 ...