kafka服务端--启动入口分析

一, 概述

kafka的broker节点大致包涵了一下模块

  • zk连接器:KafkaZkClient
  • 动态配置管理:dynamicConfigManager,dynamicConfigHandlers,这个是为了解决修改配置需要重启的问题,提取了部分配置,并不是所有配置都可以不重启生效。依赖了zkClient
  • 定时调度器:kafkaScheduler,该调度器是全局共享的。 承担了logManager模块中的日志归档,日志刷新、检查点恢复,日志的offset检查,过期日志的删除。ReplicaManager模块中的高水位检查,ISR过期检查,ISR变更处理,空闲ISR停止
  • 配额管理:quotaManagers ,限制了客户端生产、消费者消费、副本复制的流控。
  • 日志管理服务:logManager,logDirFailureChannel ,主要是本地日志的生成,清理,滚动。依赖了kafkaScheduler,KafkaZkClient
  • 元数据管理:metadataCache ,顾名思义就是针对topic元数据的缓存
  • RPC服务:socketServer,接口RPC请求的服务器。连接管理,请求分发和响应消息返回。新增了单IP的请求限制及溢出水位控制。
  • 副本管理:replicaManager ,副本的追加日志、删除日志,ISR的各种状态变更检查。依赖了KafkaZkClient,kafkaScheduler,logManager,quotaManagers,metadataCache。
  • 请求认证管理:tokenManager ,这个是权限相关,如果不开启的话,一般不用。
  • 控制器:kafkaController,这个是非常核心的,全集群唯一的控制器,控制所有的元数据变更管理,具体内容单章来说明。依赖了tokenManager,KafkaZkClient
  • 管理命令服务:adminManager,针对管理员命令的服务处理,命令行方式的创建topic,删除topic,增加分区等。依赖了 metadataCache,KafkaZkClient模块
  • 消费者协调者:groupCoordinator ,每个消费组,在服务都需要一个全局的消费组协调者,解决消费者的rebalance问题。依赖了replicaManager,KafkaZkClient模块
  • 事务协调者:transactionCoordinator,数据提交写入到本地,到复制到其他节点等全部环节中的事务管理器。依赖了replicaManager,metadataCache。
  • 请求处理线程池:requestHandlerPool , 处理来之socketServer中的请求,再调用KafkaApis处理具体的数据。依赖了socketServer,apis。
  • 业务处理服务:KafkaApis,所有请求的处理入口。依赖了socketServer,replicaManager,adminManager,groupCoordinator,transactionCoordinator,fetchManager,tokenManager.可以说这个类是整个集群的统一处理的地方。

整个服务的状态流程是通过BrokerState类来控制。

启动流转过程是:NotRunning -> Starting -> RecoveringFromUncleanShutdown(非必要) -> RunningAsBroker .

停止流转过程是:RunningAsBroker -> BrokerShuttingDown -> PendingControlledShutdown -> NotRunning

服务的启动:startup

服务的停止:shutdown

其他方法都是延伸自这两个方法

startup顺序

  1. 状态 NotRunning -> Starting
  2. initZkClient //初始化Z看
  3. getOrGenerateClusterId //在ZK 上初始化clusterID
  4. getBrokerIdAndOfflineDirs //生成brokerID
  5. KafkaScheduler.startup //初始化全局定时器
  6. quotaManagers = QuotaFactory.instantiate //初始化配额管理服务
  7. logManager.startup() //初始化日志管理服务
  8. 状态 Starting -> RecoveringFromUncleanShutdown  // 如果需要
  9. new MetadataCache //新建元数据缓存
  10. new SocketServer //新建RPC服务
  11. createReplicaManager //创建副本管理服务
  12. kafkaController.startup() //初始化控制器服务
  13. new AdminManager //新建管理员命令服务
  14. groupCoordinator.startup() //消费者组协调者器
  15. transactionCoordinator.startup() //事务协调者
  16. new KafkaApis //新建请求服务类
  17. new KafkaRequestHandlerPool //RPCServer请求的处理线程
  18. dynamicConfigHandlers //动态配置处理类
  19. new DynamicConfigManager //动态配置管理
  20. socketServer.startProcessors() //启动RPC服务
  21. 状态 Starting/RecoveringFromUncleanShutdown -> RunningAsBroker

shutdown顺序

  1. 状态 RunningAsBroker -> BrokerShuttingDown
  2. dynamicConfigManager.shutdown()
  3. socketServer.stopProcessingRequests()
  4. requestHandlerPool.shutdown()
  5. kafkaScheduler.shutdown()
  6. apis.close()
  7. adminManager.shutdown()
  8. transactionCoordinator.shutdown()
  9. groupCoordinator.shutdown()
  10. tokenManager.shutdown()
  11. replicaManager.shutdown()
  12. adminManager.shutdown()
  13. groupCoordinator.shutdown()
  14. logManager.shutdown()
  15. logManager.shutdown()
  16. zkUtils.close()
  17. metrics.close()
  18. 状态 BrokerShuttingDown -> NotRunning

总结的说:

日志管理服务logManager和副本管理replicaManager共享了同一定时器kafkaScheduler,transactionCoordinator自己独立维护了一个定时器,初始线程数时1.

对于服务的停止,broker使用了一个CountDownLatch来做标示

 

二, 源码分析

平常我们通过命令 kafka-server-start /usr/local/etc/kafka/server.properties 启动,kafka的启动类是Kafka.scala,最终会调用Kafka.scala类的main方法。

另外,启动脚本中还会设置相关JVM参数,如log4j配置文件地址、JVM堆大小等等

下面通过源码简单分析下kafka的启动流程以及shutdown的实现。源码分析本节都以0.10.2版本为准

启动入口

//Kafka.scala
def main(args: Array[String]): Unit = {
  try {
	//根据命令行的参数,获取配置文件中的相关配置,这里获取到的也就是/usr/local/etc/kafka/server.properties的内容
	//这里同时还会检查是否有入参,如果没有就会报错
    val serverProps = getPropsFromArgs(args)
	//根据配置构造一个kafkaServerStartable对象,这里面会检验必要的参数是否有值
    val kafkaServerStartable = KafkaServerStartable.fromProps(serverProps)

    //绑定一个进程关闭的钩子
    Runtime.getRuntime().addShutdownHook(new Thread() {
      override def run() = {
        kafkaServerStartable.shutdown
      }
    })
	//在KafkaServerStartable.scala的startup方法中,会继续调用KafkaServer#startup()方法
	//在KafkaServer#startup()方法中,开始初始化并加载各个组件
    kafkaServerStartable.startup
	//阻塞直到kafka被关闭
	//底层用了java的CountDownLatch.await()。当kafka被关闭时,对应的CountDownLatch.countDown()方法会被调用,这时候程序就会真正退出
    kafkaServerStartable.awaitShutdown
  }
  catch {
    case e: Throwable =>
      fatal(e)
      System.exit(1)
  }
  System.exit(0)
}

broker的生命周期

kafka的生命周期中的各个状态如下图:

*
*                +-----------+
*                |Not Running|
*                +-----+-----+
*                      |
*                      v
*                +-----+-----+
*                |Starting   +--+
*                +-----+-----+  | +----+------------+
*                      |        +>+RecoveringFrom   |
*                      v          |UncleanShutdown  |
*               +-------+-------+ +-------+---------+
*               |RunningAsBroker|            |
*               +-------+-------+<-----------+
*                       |
*                       v
*                +-----+------------+
*                |PendingControlled |
*                |Shutdown          |
*                +-----+------------+
*                      |
*                      v
*               +-----+----------+
*               |BrokerShutting  |
*               |Down            |
*               +-----+----------+
*                     |
*                     v
*               +-----+-----+
*               |Not Running|
*               +-----------+
*
  • NOT Running : 未运行状态
  • Starting:正在启动中
  • RunningAsBroker: broker在运行中
  • RecoveringFromUncleanShutdown:从上次不完整的关闭中恢复状态,这个状态和logManager有关
  • PendingControlledShutdown:broker向controller报告关闭,等待controller应答中
  • BrokerShuttingDown:broker在关闭中

sever启动流程

//KafkaServer#startup()
def startup() {
  try {
    info("starting")

    if (isShuttingDown.get)
      throw new IllegalStateException("Kafka server is still shutting down, cannot re-start!")

    if (startupComplete.get)
      return

    val canStartup = isStartingUp.compareAndSet(false, true)
    if (canStartup) {
      //设置broker状态为Starting
      brokerState.newState(Starting)

      //启动一个定时任务的线程池
      kafkaScheduler.startup()

      //初始化zk组件,后续用于监听、获取zk数据用
      zkUtils = initZk()

      //获取集群的id,如果当前集群尚未生成集群id,那就生成一个,对应zk的 /cluster/id 的值
      _clusterId = getOrGenerateClusterId(zkUtils)
      info(s"Cluster ID = $clusterId")

      //获取或者生成一个brokerId
      config.brokerId = getBrokerId
      this.logIdent = "[Kafka Server " + config.brokerId + "], "

      //创建一个用于度量的组件
      val reporters = config.getConfiguredInstances(KafkaConfig.MetricReporterClassesProp, classOf[MetricsReporter],
        Map[String, AnyRef](KafkaConfig.BrokerIdProp -> (config.brokerId.toString)).asJava)
      reporters.add(new JmxReporter(jmxPrefix))
      val metricConfig = KafkaServer.metricConfig(config)
      metrics = new Metrics(metricConfig, reporters, time, true)

      quotaManagers = QuotaFactory.instantiate(config, metrics, time)
      notifyClusterListeners(kafkaMetricsReporters ++ reporters.asScala)

      //创建日志管理组件,创建时会检查log目录下是否有.kafka_cleanshutdown文件,如果没有的话,broker进入RecoveringFrom UncleanShutdown 状态
      logManager = createLogManager(zkUtils.zkClient, brokerState)
      logManager.startup()

      //创建元数据管理组件
      metadataCache = new MetadataCache(config.brokerId)
      //创建凭证提供者组件
      credentialProvider = new CredentialProvider(config.saslEnabledMechanisms)

      //创建一个sockerServer组件,并启动。该组件启动后,就会开始接收rpc请求了
      socketServer = new SocketServer(config, metrics, time, credentialProvider)
      socketServer.startup()

      //创建一个副本管理组件,并启动该组件
      replicaManager = new ReplicaManager(config, metrics, time, zkUtils, kafkaScheduler, logManager,
        isShuttingDown, quotaManagers.follower)
      replicaManager.startup()

      //创建kafka控制器,并启动。该控制器启动后broker会尝试去zk创建节点竞争成为controller
      kafkaController = new KafkaController(config, zkUtils, brokerState, time, metrics, threadNamePrefix)
      kafkaController.startup()
      //创建一个集群管理组件
      adminManager = new AdminManager(config, metrics, metadataCache, zkUtils)

      //创建群组协调器,并且启动
      groupCoordinator = GroupCoordinator(config, zkUtils, replicaManager, Time.SYSTEM)
      groupCoordinator.startup()

      //构造授权器
      authorizer = Option(config.authorizerClassName).filter(_.nonEmpty).map { authorizerClassName =>
        val authZ = CoreUtils.createObject[Authorizer](authorizerClassName)
        authZ.configure(config.originals())
        authZ
      }

      //构造api组件,针对各个接口会处理不同的业务
      apis = new KafkaApis(socketServer.requestChannel, replicaManager, adminManager, groupCoordinator,
        kafkaController, zkUtils, config.brokerId, config, metadataCache, metrics, authorizer, quotaManagers,
        clusterId, time)
      //请求处理池
      requestHandlerPool = new KafkaRequestHandlerPool(config.brokerId, socketServer.requestChannel, apis, time,
        config.numIoThreads)

      Mx4jLoader.maybeLoad()

      //动态配置处理器的相关配置
      dynamicConfigHandlers = Map[String, ConfigHandler](ConfigType.Topic -> new TopicConfigHandler(logManager, config, quotaManagers),
        ConfigType.Client -> new ClientIdConfigHandler(quotaManagers),
        ConfigType.User -> new UserConfigHandler(quotaManagers, credentialProvider),
        ConfigType.Broker -> new BrokerConfigHandler(config, quotaManagers))

      //初始化动态配置管理器,并启动
      dynamicConfigManager = new DynamicConfigManager(zkUtils, dynamicConfigHandlers)
      dynamicConfigManager.startup()

      /* tell everyone we are alive */
      val listeners = config.advertisedListeners.map { endpoint =>
        if (endpoint.port == 0)
          endpoint.copy(port = socketServer.boundPort(endpoint.listenerName))
        else
          endpoint
      }
	  //kafka健康检查组件
      kafkaHealthcheck = new KafkaHealthcheck(config.brokerId, listeners, zkUtils, config.rack,
        config.interBrokerProtocolVersion)
      kafkaHealthcheck.startup()

      //记录一下恢复点
      checkpointBrokerId(config.brokerId)

      /* register broker metrics */
      registerStats()
	  //broker进入RunningAsBroker状态
      brokerState.newState(RunningAsBroker)
      shutdownLatch = new CountDownLatch(1)
      startupComplete.set(true)
      isStartingUp.set(false)
      AppInfoParser.registerAppInfo(jmxPrefix, config.brokerId.toString)
      info("started")
    }
  }
  catch {
    case e: Throwable =>
      fatal("Fatal error during KafkaServer startup. Prepare to shutdown", e)
      isStartingUp.set(false)
      shutdown()
      throw e
  }
}

启动流程就是初始化一堆组件,然后该启动的启动。这些组件以后我会一个一个介绍,现在大家先简单了解一下它们的启动过程就好了。

shutdown实现

在启动代码那里,可以看到kafka在那里面已经做了shutdown的钩子。当kafka关闭的是会执行kafkaServerStartable#shutdown()方法。最后调用了KafkaServer#shutdown()方法

def shutdown() {
  try {
    info("shutting down")

    if (isStartingUp.get)
      throw new IllegalStateException("Kafka server is still starting up, cannot shut down!")

    // To ensure correct behavior under concurrent calls, we need to check `shutdownLatch` first since it gets updated
    // last in the `if` block. If the order is reversed, we could shutdown twice or leave `isShuttingDown` set to
    // `true` at the end of this method.
    if (shutdownLatch.getCount > 0 && isShuttingDown.compareAndSet(false, true)) {
	  //controlledShutdown()里面会通知controller自己关闭了,会一直阻塞到通知成功,这时候broker会进入PendingControlled Shutdown状态
      CoreUtils.swallow(controlledShutdown())
	  //broker进入BrokerShutting Down状态
      brokerState.newState(BrokerShuttingDown)
      if (socketServer != null)
        CoreUtils.swallow(socketServer.shutdown())
      if (requestHandlerPool != null)
        CoreUtils.swallow(requestHandlerPool.shutdown())
      CoreUtils.swallow(kafkaScheduler.shutdown())
      if (apis != null)
        CoreUtils.swallow(apis.close())
      CoreUtils.swallow(authorizer.foreach(_.close()))
      if (replicaManager != null)
        CoreUtils.swallow(replicaManager.shutdown())
      if (adminManager != null)
        CoreUtils.swallow(adminManager.shutdown())
      if (groupCoordinator != null)
        CoreUtils.swallow(groupCoordinator.shutdown())
      if (logManager != null)
        CoreUtils.swallow(logManager.shutdown())
      if (kafkaController != null)
        CoreUtils.swallow(kafkaController.shutdown())
      if (zkUtils != null)
        CoreUtils.swallow(zkUtils.close())
      if (metrics != null)
        CoreUtils.swallow(metrics.close())
	  //broker进入Not Running状态
      brokerState.newState(NotRunning)

      startupComplete.set(false)
      isShuttingDown.set(false)
      CoreUtils.swallow(AppInfoParser.unregisterAppInfo(jmxPrefix, config.brokerId.toString))
      shutdownLatch.countDown()
      info("shut down completed")
    }
  }
  catch {
    case e: Throwable =>
      fatal("Fatal error during KafkaServer shutdown.", e)
      isShuttingDown.set(false)
      throw e
  }
}

做的事情就是将之前的那些组件优雅的关闭掉。在关闭这些组件前,可能还会去通知controller自己关闭了,收到controller答复后再继续关闭剩下的组件。

向controller报告关闭

如果 controlled.shutdown.enable 开启的话,broker在关闭时会向controller报告自己关闭,这样controller可以对broker的下线及时做一些操作,比如partition的重新选举、分区副本的关闭、通知其他的broker元数据变动等。该配置默认是开启的。

broker向controller报告关闭的流程是这样的:

  1. 检查controlled.shutdown.enable是否开启,如果开启,broker就进入PendingControlledShutdown状态,同时开始向controller发送shutdown请求
  2. 检查重试次数是否达到controlled.shutdown.max.retries配置的值,如果是的话则退出不在发送shutdown请求。重试次数默认是3次
  3. 从zk获取当前controller所在的id,然后向该节点发送shutdown请求
  4. 如果请求失败,休眠controlled.shutdown.retry.backoff.ms毫秒后,重试次数+1,重新进入步骤二
  5. 如果请求成功,说明controller接受到了shutdown请求,并处理完成。
posted @ 2022-03-10 20:43  車輪の唄  阅读(34)  评论(0编辑  收藏  举报  来源