spark core原始碼分析3 Master HA

五柳-先生發表於2016-01-29

這一節講解master 選舉以及之後的處理流程

上一節說到在Master啟動過程中,首先呼叫了 Akka actor的preStart方法。

[java] view plain copy
  1. override def preStart() {  
  2.   logInfo("Starting Spark master at " + masterUrl)  
  3.   logInfo(s"Running Spark version ${org.apache.spark.SPARK_VERSION}")  
  4.   // Listen for remote client disconnection events, since they don't go through Akka's watch()  
  5.   context.system.eventStream.subscribe(self, classOf[RemotingLifecycleEvent])  
  6.   webUi.bind()  
  7.   masterWebUiUrl = "http://" + masterPublicAddress + ":" + webUi.boundPort  
  8.   context.system.scheduler.schedule(0 millis, WORKER_TIMEOUT millis, self, CheckForWorkerTimeOut)  
  9.   
  10.   masterMetricsSystem.registerSource(masterSource)  
  11.   masterMetricsSystem.start()  
  12.   applicationMetricsSystem.start()  
  13.   // Attach the master and app metrics servlet handler to the web ui after the metrics systems are  
  14.   // started.  
  15.   masterMetricsSystem.getServletHandlers.foreach(webUi.attachHandler)  
  16.   applicationMetricsSystem.getServletHandlers.foreach(webUi.attachHandler)  
  17.   
  18.   //HA的流程從這裡開始  
  19.   //這裡可以選擇Master的後設資料資訊儲存在哪裡,我們以ZK為例講解  
  20.   //這裡用Apache Curator作為zk的client,它包裝了zk client 複雜的api  
  21.   val (persistenceEngine_, leaderElectionAgent_) = RECOVERY_MODE match {  
  22.     case "ZOOKEEPER" =>  
  23.       logInfo("Persisting recovery state to ZooKeeper")  
  24.       val zkFactory =  
  25.         new ZooKeeperRecoveryModeFactory(conf, SerializationExtension(context.system))  
  26.       (zkFactory.createPersistenceEngine(), zkFactory.createLeaderElectionAgent(this))  
  27.     case "FILESYSTEM" =>  
  28.       val fsFactory =  
  29.         new FileSystemRecoveryModeFactory(conf, SerializationExtension(context.system))  
  30.       (fsFactory.createPersistenceEngine(), fsFactory.createLeaderElectionAgent(this))  
  31.     case "CUSTOM" =>  
  32.       val clazz = Class.forName(conf.get("spark.deploy.recoveryMode.factory"))  
  33.       val factory = clazz.getConstructor(classOf[SparkConf], classOf[Serialization])  
  34.         .newInstance(conf, SerializationExtension(context.system))  
  35.         .asInstanceOf[StandaloneRecoveryModeFactory]  
  36.       (factory.createPersistenceEngine(), factory.createLeaderElectionAgent(this))  
  37.     case _ =>  
  38.       (new BlackHolePersistenceEngine(), new MonarchyLeaderAgent(this))  
  39.   }  
  40.   persistenceEngine = persistenceEngine_  
  41.   leaderElectionAgent = leaderElectionAgent_  
  42. }  

上面的persistenceEngine_封裝了在zk中讀寫後設資料資訊,以及序列化反序列化的介面

leaderElectionAgent_封裝了master的選舉過程,見下面程式碼註釋中的解釋

  1. private[master] class ZooKeeperLeaderElectionAgent(val masterActor: LeaderElectable,  
  2.     conf: SparkConf) extends LeaderLatchListener with LeaderElectionAgent with Logging  {  
  3.   
  4.   //依賴zk中的一個節點來判斷選主  
  5.   val WORKING_DIR = conf.get("spark.deploy.zookeeper.dir""/spark") + "/leader_election"  
  6.   
  7.   private var zk: CuratorFramework = _  
  8.   private var leaderLatch: LeaderLatch = _  
  9.   private var status = LeadershipStatus.NOT_LEADER  
  10.   
  11.   //構造這個物件之後就呼叫了start方法  
  12.   start()  
  13.   //leaderLatch.start()一旦呼叫,LeaderLatch會和其它使用相同latch path的其它LeaderLatch交涉,然後隨機的選擇其中一個作為leader  
  14.   private def start() {  
  15.     logInfo("Starting ZooKeeper LeaderElection agent")  
  16.     zk = SparkCuratorUtil.newClient(conf)  
  17.     leaderLatch = new LeaderLatch(zk, WORKING_DIR)  
  18.     leaderLatch.addListener(this)  
  19.     leaderLatch.start()  
  20.   }  
  21.   
  22.   override def stop() {  
  23.     leaderLatch.close()  
  24.     zk.close()  
  25.   }  
  26.   
  27.   //當一個master被選為主時,isLeader方法被回撥,說明在這一輪選舉中勝出  
  28.   override def isLeader() {  
  29.     synchronized {  
  30.       // could have lost leadership by now.  
  31.       if (!leaderLatch.hasLeadership) {  
  32.         return  
  33.       }  
  34.   
  35.       logInfo("We have gained leadership")  
  36.       updateLeadershipStatus(true)  
  37.     }  
  38.   }  
  39.   
  40.   //當一個master被選為備時,notLeader方法被回撥,說明在這一輪選舉中落敗  
  41.   override def notLeader() {  
  42.     synchronized {  
  43.       // could have gained leadership by now.  
  44.       if (leaderLatch.hasLeadership) {  
  45.         return  
  46.       }  
  47.   
  48.       logInfo("We have lost leadership")  
  49.       updateLeadershipStatus(false)  
  50.     }  
  51.   }  
  52.   private def updateLeadershipStatus(isLeader: Boolean) {  
  53.     //當一個master之前狀態為備,目前被選為主  
  54.     if (isLeader && status == LeadershipStatus.NOT_LEADER) {  
  55.       status = LeadershipStatus.LEADER  
  56.       masterActor.electedLeader()//呼叫master類的electedLeader方法      
  57.       //當一個master之前狀態為主,目前被選為備  
  58.     } else if (!isLeader && status == LeadershipStatus.LEADER) {  
  59.       status = LeadershipStatus.NOT_LEADER  
  60.       masterActor.revokedLeadership()//呼叫master類的revokedLeadership方法      
  61.     }  
  62.   }  
  63.   
  64.   private object LeadershipStatus extends Enumeration {  
  65.     type LeadershipStatus = Value  
  66.     val LEADER, NOT_LEADER = Value  
  67.   }  
  68. }  
繼續檢視master中的邏輯
  1. override def receiveWithLogging: PartialFunction[Any, Unit] = {  
  2.   case ElectedLeader => {  
  3.     //既然之前是備,現在想變成主,就需要讀取zk中的必要的資訊來構造後設資料  
  4.     val (storedApps, storedDrivers, storedWorkers) = persistenceEngine.readPersistedData()  
  5.     state = if (storedApps.isEmpty && storedDrivers.isEmpty && storedWorkers.isEmpty) {  
  6.       RecoveryState.ALIVE//如果沒有任何後設資料需要構造,則直接置為alive狀態  
  7.     } else {  
  8.       RecoveryState.RECOVERING//不然需要置為恢復中  
  9.     }  
  10.     logInfo("I have been elected leader! New state: " + state)  
  11.     if (state == RecoveryState.RECOVERING) {  
  12.       beginRecovery(storedApps, storedDrivers, storedWorkers)//見下面介紹  
  13.       recoveryCompletionTask = context.system.scheduler.scheduleOnce(WORKER_TIMEOUT millis, self,  
  14.         CompleteRecovery)  
  15.     }  
  16.   }  
  17.   
  18.   case CompleteRecovery => completeRecovery()  
  19.   
  20.   //之前是主,現在被置為備了,不需要額外操作,退出即可  
  21.   case RevokedLeadership => {  
  22.     logError("Leadership has been revoked -- master shutting down.")  
  23.     System.exit(0)  
  24.   }  
開始恢復
  1. private def beginRecovery(storedApps: Seq[ApplicationInfo], storedDrivers: Seq[DriverInfo],  
  2.     storedWorkers: Seq[WorkerInfo]) {  
  3.   for (app <- storedApps) {  
  4.     logInfo("Trying to recover app: " + app.id)  
  5.     try {  
  6.       registerApplication(app)//將讀到的app載入到記憶體  
  7.       app.state = ApplicationState.UNKNOWN//狀態置為unknown  
  8.       app.driver ! MasterChanged(masterUrl, masterWebUiUrl)//向driver傳送MasterChanged訊息  
  9.     } catch {  
  10.       case e: Exception => logInfo("App " + app.id + " had exception on reconnect")  
  11.     }  
  12.   }  
  13.   
  14.   for (driver <- storedDrivers) {  
  15.     // Here we just read in the list of drivers. Any drivers associated with now-lost workers  
  16.     // will be re-launched when we detect that the worker is missing.  
  17.     drivers += driver//將讀到的driver載入到記憶體  
  18.   }  
  19.   
  20.   for (worker <- storedWorkers) {  
  21.     logInfo("Trying to recover worker: " + worker.id)  
  22.     try {  
  23.       registerWorker(worker)//將讀到的worker資訊載入到記憶體  
  24.       worker.state = WorkerState.UNKNOWN//同樣狀態需要置為unknown,需要等到worker傳送訊息過來之後才能認為該worker是可用的  
  25.       worker.actor ! MasterChanged(masterUrl, masterWebUiUrl)//向worker傳送MasterChanged訊息  
  26.     } catch {  
  27.       case e: Exception => logInfo("Worker " + worker.id + " had exception on reconnect")  
  28.     }  
  29.   }  
  30. }  
看driver端收到MasterChanged訊息會發生什麼?在AppClient.scala中
只有主master會傳送MasterChanged訊息,所以這裡的masterUrl肯定是新的主master的
  1. case MasterChanged(masterUrl, masterWebUiUrl) =>  
  2.   logInfo("Master has changed, new master is at " + masterUrl)  
  3.   //收到這個訊息之後,driver需要修改之前儲存的master資訊,用於之後向新的master通訊  
  4.   changeMaster(masterUrl)  
  5.   alreadyDisconnected = false  
  6.   sender ! MasterChangeAcknowledged(appId)//向master反饋MasterChangeAcknowledged訊息  
master這時會收到所有app中driver發來的訊息,我們看master收到MasterChangeAcknowledged訊息的處理方式,引數為appId
  1. case MasterChangeAcknowledged(appId) => {  
  2.   idToApp.get(appId) match {  
  3.     case Some(app) =>  
  4.       logInfo("Application has been re-registered: " + appId)  
  5.       app.state = ApplicationState.WAITING  //收到訊息後將app狀態置為WAITING  
  6.     case None =>  
  7.       logWarning("Master change ack from unknown app: " + appId)  
  8.   }  
  9.   
  10.   if (canCompleteRecovery) { completeRecovery() }  //這個只是優先判斷訊息處理是否都結束了,這樣就不用等待worker_timeout的時間間隔再呼叫completeRecovery了  
  11. }  
看worker端收到MasterChanged訊息會發生什麼?在Worker.scala中
  1. case MasterChanged(masterUrl, masterWebUiUrl) =>  
  2.   logInfo("Master has changed, new master is at " + masterUrl)  
  3.   changeMaster(masterUrl, masterWebUiUrl)//同上  
  4.   
  5. //master不與Executor互動,所以需要worker來告訴master關於Executor的資訊  
  6. val execs = executors.values.  
  7.     map(e => new ExecutorDescription(e.appId, e.execId, e.cores, e.state))  
  8.   sender ! WorkerSchedulerStateResponse(workerId, execs.toList, drivers.keys.toSeq)  
繼續看master中的處理邏輯
  1. case WorkerSchedulerStateResponse(workerId, executors, driverIds) => {  
  2.   idToWorker.get(workerId) match {  
  3.     case Some(worker) =>  
  4.       logInfo("Worker has been re-registered: " + workerId)  
  5.       worker.state = WorkerState.ALIVE //這時可以將之前worker狀態unknown修改為ALIVE,代表該worker可用  
  6.   
  7.       //將接受到的Executor資訊更新到相關的app,worker中  
  8.       val validExecutors = executors.filter(exec => idToApp.get(exec.appId).isDefined)  
  9.       for (exec <- validExecutors) {  
  10.         val app = idToApp.get(exec.appId).get  
  11.         val execInfo = app.addExecutor(worker, exec.cores, Some(exec.execId))  
  12.         worker.addExecutor(execInfo)  
  13.         execInfo.copyState(exec)  
  14.       }  
  15.   
  16.       //將master中driver資訊更新,狀態置為RUNNING  
  17.       for (driverId <- driverIds) {  
  18.         drivers.find(_.id == driverId).foreach { driver =>  
  19.           driver.worker = Some(worker)  
  20.           driver.state = DriverState.RUNNING  
  21.           worker.drivers(driverId) = driver  
  22.         }  
  23.       }  
  24.     case None =>  
  25.       logWarning("Scheduler state from unknown worker: " + workerId)  
  26.   }  
  27.   
  28.   if (canCompleteRecovery) { completeRecovery() }  //同上  
  29. }  
這一切都處理完畢之後,看master的completeRecovery,這個是在beginRecovery呼叫之後,在延遲worker_timeout時間之後呼叫,一般情況下,上面的訊息來回傳送處理應該都已經結束了
  1. private def completeRecovery() {  
  2.   // Ensure "only-once" recovery semantics using a short synchronization period.  
  3.   synchronized {  
  4.     if (state != RecoveryState.RECOVERING) { return }  
  5.     state = RecoveryState.COMPLETING_RECOVERY//狀態置為恢復完成  
  6.   }  
  7.   
  8.   // Kill off any workers and apps that didn't respond to us.  
  9.   //清理在這個worker_timeout間隔過後還未處理成功的worker和app  
  10.   workers.filter(_.state == WorkerState.UNKNOWN).foreach(removeWorker)  
  11.   apps.filter(_.state == ApplicationState.UNKNOWN).foreach(finishApplication)  
  12.   
  13.   // Reschedule drivers which were not claimed by any workers  
  14.   //在一番訊息通訊之後,本應該在driver中更新的worker資訊不見了,則重啟driver或者刪除  
  15.   drivers.filter(_.worker.isEmpty).foreach { d =>  
  16.     logWarning(s"Driver ${d.id} was not found after master recovery")  
  17.     if (d.desc.supervise) {  
  18.       logWarning(s"Re-launching ${d.id}")  
  19.       relaunchDriver(d)  
  20.     } else {  
  21.       removeDriver(d.id, DriverState.ERROR, None)  
  22.       logWarning(s"Did not re-launch ${d.id} because it was not supervised")  
  23.     }  
  24.   }  
  25.   
  26.   state = RecoveryState.ALIVE  //這時恢復狀態真正結束了  
  27.   schedule() //整個選主流程結束時候,重新排程一次  
  28.   logInfo("Recovery complete - resuming operations!")  
  29. }  

轉載:http://blog.csdn.net/yueqian_zhu/article/details/47954705

相關文章