kafka原始碼剖析(二)之kafka-server的啟動

全面攻城發表於2018-03-15

KAFKA的啟動

直接執行Kafka.scala中的main方法(需要指定啟動引數,也就是server.properties的位置)來啟動Kafka。因為kafka依賴zookeeper,所以我們需要提前啟動zookeeper,然後在server.properties中指定zk地址後,啟動。

看一下main()方法:

  def main(args: Array[String]): Unit = {
    try {   
// 載入對應的server.properties配置檔案,並生成Properties例項.
      val serverProps = getPropsFromArgs(args)
//這裡生成一個KafkaServer的例項,這個例項生成時,會在例項中同時生成一個KafkaServer的例項,
// 生成KafkaServer例項前,需要先通過serverProps生成出一個KafkaConfig的例項.

      val kafkaServerStartable = KafkaServerStartable.fromProps(serverProps)

      // attach shutdown handler to catch control-c
      Runtime.getRuntime().addShutdownHook(new Thread() {
        override def run() = {
          kafkaServerStartable.shutdown
        }
      })
// 停止 服務 
      kafkaServerStartable.startup
      kafkaServerStartable.awaitShutdown
    }
    catch {
      case e: Throwable =>
        fatal(e)
        System.exit(1)
    }
    System.exit(0)
  }
複製程式碼

根據properties生成server例項

getPropsFromArgs(args),這一行很明確,就是從配置檔案中讀取我們配置的內容,然後賦值給serverProps。 KafkaServerStartable.fromProps(serverProps),

    object KafkaServerStartable {
      def fromProps(serverProps: Properties) = {
        KafkaMetricsReporter.startReporters(new VerifiableProperties(serverProps))
        new KafkaServerStartable(KafkaConfig.fromProps(serverProps))
      }
    }
複製程式碼

這塊主要是啟動了一個內部的監控服務(內部狀態監控)。

KafkaServer的啟動

下面是一個在java中常見的鉤子函式,在關閉時會啟動一些銷燬程式,保證程式安全關閉。kafkaServerStartable.startup。跟進去可以很清楚的看到,裡面呼叫的方法是KafkaServer中的startup方法:

// 啟動kafka的排程器,這個KafkaScheduler的例項生成時需要得到background.threads配置的值,預設是10個,用於配置後臺執行緒池的個數

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) {
        metrics = new Metrics(metricConfig, reporters, kafkaMetricsTime, true)

        brokerState.newState(Starting)
    // 啟動scheduler 例項  
        /* start scheduler */
        kafkaScheduler.startup()
   
//  生產zk 初始化 並依賴  判斷  broker 是否發生變化  
        /* setup zookeeper */
        zkUtils = initZk()
     
     // 初始化建立並啟動LogManager的例項,
        /* start log manager */
        logManager = createLogManager(zkUtils.zkClient, brokerState)
        logManager.startup()

// 如果broker.id的配置沒有配置(小於0的值時),同時broker.id.generation.enable配置為true,預設也就是true,
// 這個時候根據zk中/brokers/seqid路徑的version值,第一次從0開始,每次增加.並加上reserved.broker.max.id配置的值,預設是1000,
//來充當這個server的broker.id,同時把這個broker.id更新到logDir目錄下的meta.properties檔案中,
//下次讀取時,直接讀取這個配置檔案中的broker.id的值,而不需要重新進行建立.
        /* generate brokerId */
        config.brokerId =  getBrokerId
        this.logIdent = "[Kafka Server " + config.brokerId + "], "
  // 啟動 kafka 的sockerServer 
        socketServer = new SocketServer(config, metrics, kafkaMetricsTime)
        socketServer.startup()

//,生成並啟動ReplicaManager,此例項依賴kafkaScheduler與logManager例項.
       /* start replica manager */
        replicaManager = new ReplicaManager(config, metrics, time, kafkaMetricsTime, zkUtils, kafkaScheduler, logManager,
          isShuttingDown)
        replicaManager.startup()

//生成並啟動KafkaController例項,此使用用於控制當前的broker中的所有的leader的partition的操作.        
   /* start kafka controller */
        kafkaController = new KafkaController(config, zkUtils, brokerState, kafkaMetricsTime, metrics, threadNamePrefix)
        kafkaController.startup()

   //生成並啟動GroupCoordinator的例項,這個是0.9新加入的一個玩意,用於對consumer中新加入的與partition的檢查,並對partition與consumer進行平衡操作.

        /* start group coordinator */
        groupCoordinator = GroupCoordinator(config, zkUtils, replicaManager, kafkaMetricsTime)
        groupCoordinator.startup()

    // 根據authorizer.class.name配置項配置的Authorizer的實現類,生成一個用於認證的例項,用於對使用者的操作進行認證.這個預設為不認證.
        /* Get the authorizer and initialize it if one is specified.*/
        authorizer = Option(config.authorizerClassName).filter(_.nonEmpty).map { authorizerClassName =>
          val authZ = CoreUtils.createObject[Authorizer](authorizerClassName)
          authZ.configure(config.originals())
          authZ
        }

   // 成用於對外對外提供服務的KafkaApis例項,並設定當前的broker的狀態為執行狀態
        /* start processing requests */
        apis = new KafkaApis(socketServer.requestChannel, replicaManager, groupCoordinator,
          kafkaController, zkUtils, config.brokerId, config, metadataCache, metrics, authorizer)
        requestHandlerPool = new KafkaRequestHandlerPool(config.brokerId, socketServer.requestChannel, apis, config.numIoThreads)
        brokerState.newState(RunningAsBroker)

        Mx4jLoader.maybeLoad()


//生成動態配置修改的處理管理,主要是topic修改與client端配置的修改,並把已經存在的clientid對應的配置進行修改.
        /* start dynamic config manager */
        dynamicConfigHandlers = Map[String, ConfigHandler](ConfigType.Topic -> new TopicConfigHandler(logManager, config),
                                                           ConfigType.Client -> new ClientIdConfigHandler(apis.quotaManagers))

        // Apply all existing client configs to the ClientIdConfigHandler to bootstrap the overrides
        // TODO: Move this logic to DynamicConfigManager
        AdminUtils.fetchAllEntityConfigs(zkUtils, ConfigType.Client).foreach {
          case (clientId, properties) => dynamicConfigHandlers(ConfigType.Client).processConfigChanges(clientId, properties)
        }
//   建立一個配置例項 併發起通知給個個block
        // Create the config manager. start listening to notifications
        dynamicConfigManager = new DynamicConfigManager(zkUtils, dynamicConfigHandlers)
        dynamicConfigManager.startup()
         
        /* tell everyone we are alive */
        val listeners = config.advertisedListeners.map {case(protocol, endpoint) =>
          if (endpoint.port == 0)
            (protocol, EndPoint(endpoint.host, socketServer.boundPort(protocol), endpoint.protocolType))
          else
            (protocol, endpoint)
        }
        kafkaHealthcheck = new KafkaHealthcheck(config.brokerId, listeners, zkUtils, config.rack,
          config.interBrokerProtocolVersion)
        kafkaHealthcheck.startup()

        // Now that the broker id is successfully registered via KafkaHealthcheck, checkpoint it
        checkpointBrokerId(config.brokerId)

        /* register broker metrics */
        registerStats()

        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
    }
  }
複製程式碼

首先判斷是否目前正在關閉中或者已經啟動了,這兩種情況直接丟擲異常。然後是一個CAS的操作isStartingUp,防止執行緒併發操作啟動,判斷是否可以啟動。如果可以啟動,就開始我們的啟動過程。

構造Metrics類 定義broker狀態為啟動中starting 啟動定時器kafkaScheduler.startup() 構造zkUtils:利用引數中的zk資訊,啟動一個zk客戶端 啟動檔案管理器:讀取zk中的配置資訊,包含__consumer_offsets和__system.topic__。重點是啟動一些定時任務,來刪除符合條件的記錄(cleanupLogs),清理髒記錄(flushDirtyLogs),把所有記錄寫到一個文字檔案中,防止在啟動時重啟所有的記錄檔案(checkpointRecoveryPointOffsets)。

  /**
   *  Start the background threads to flush logs and do log cleanup
   */
  def startup() {
    /* Schedule the cleanup task to delete old logs */
    if(scheduler != null) {
      info("Starting log cleanup with a period of %d ms.".format(retentionCheckMs))
      scheduler.schedule("kafka-log-retention", 
                         cleanupLogs, 
                         delay = InitialTaskDelayMs, 
                         period = retentionCheckMs, 
                         TimeUnit.MILLISECONDS)
      info("Starting log flusher with a default period of %d ms.".format(flushCheckMs))
      scheduler.schedule("kafka-log-flusher", 
                         flushDirtyLogs, 
                         delay = InitialTaskDelayMs, 
                         period = flushCheckMs, 
                         TimeUnit.MILLISECONDS)
      scheduler.schedule("kafka-recovery-point-checkpoint",
                         checkpointRecoveryPointOffsets,
                         delay = InitialTaskDelayMs,
                         period = flushCheckpointMs,
                         TimeUnit.MILLISECONDS)
    }
    if(cleanerConfig.enableCleaner)
      cleaner.startup()
  }
複製程式碼

相關文章