Yarn -- HA源碼剖析

HA簡介

HA(High  Available),高可用性集羣,是保證單點故障問題(指單點故障會引起系統整體故障的問題)的有效解決方案,一般有兩個或兩個以上的節點,且分爲活動節點(active)及備用(standby)節點。當活動節點出現問題,導致正在運行的業務(任務)不能正常運行時,備用節點此時就會偵測到,並立即接續活動節點來執行業務,保證集羣的穩定性。

Yarn HA -- 集羣的初始化

1. 需要在主備機都執行start-yarn.sh,來啓動主備機的RM,誰優先搶注在zookeeper上,誰就作爲active節點,那在後文,將針對RM的啓動,從源碼層面做一個剖析,本文側重的HA模塊的啓動,所以在整個RM的啓動,也只介紹HA的相關模塊。

2. 那執行start-yarn.sh腳本後,就直接從ResourceManager的main()方法開始看

   ResourceManager resourceManager = new ResourceManager();
   ShutdownHookManager.get().addShutdownHook(
     new CompositeServiceShutdownHook(resourceManager),
     SHUTDOWN_HOOK_PRIORITY);
   /* 會實現本類中的serviceInit方法 */
   resourceManager.init(conf);
   resourceManager.start();

3. main方法中執行了一個初始化方法和啓動方法,那我們優先來看init()方法,Yarn中所有的init和start方法都是重寫AbstractService類中的方法來實現的,最終分別由匹配的serviceInit(config)和serviceStart()來實現。初始化方法自然是在服務啓動前,讀取一些相關的配置文件,而這個方法就是去讀取與Yarn相關的配置文件,並保存下來,內部代碼很簡單,就不做介紹了。

   //初始化啓動前,設置HA配置
   this.rmContext.setHAEnabled(HAUtil.isHAEnabled(this.conf));
   if (this.rmContext.isHAEnabled()) {
     HAUtil.verifyAndSetConfiguration(this.conf);
   }

4. 接下來來看比較重要的AdminService()類,在createAdminService()方法內就是new了一個AdminService(),ResourceManager實際上是由許多子服務組成的一個大服務,在RM的初始化中都使用addService(adminService)方法來添加子服務,最終統一調用所有子服務的初始化方法,來初始化各個子服務,本篇文章中與HA交互最深的就是AdminService(),所以接下來看一下它的init()方法

   adminService = createAdminService();
   addService(adminService);
   rmContext.setRMAdminService(adminService);

5. 雖然這裏對HA的一些配置文件做了判斷,首先是做了是否開啓故障轉移的判斷,默認是開啓的,如果關閉了在RM的主機故障後需要使用yarn rmadmin -transitionToActive rm2命令來切換主備機,其次判斷了是否使用內置的故障轉移,但是並沒有看到不使用內置的自動故障轉移後有別的邏輯,所以從這裏可以看出yarn ha與hdfs ha有所區別,Yarn的HA應該算是一種輕量級的HA,也發現Yarn HA的機制似乎不夠完善。那這裏自然要去分析Yarn內部的故障轉移服務,createEmbeddedElectorService()裏new了一個new EmbeddedElectorService(rmContext),那來看它的init方法

       //判斷是否是HA
       if (rmContext.isHAEnabled()) {
         //是否開啓自動故障切換模式,默認是true
         autoFailoverEnabled = HAUtil.isAutomaticFailoverEnabled(conf);
         if (autoFailoverEnabled) {
           //是否開啓內置的自動故障切換模式,默認是true
           if (HAUtil.isAutomaticFailoverEmbedded(conf)) {
             //使用此故障轉移服務
             embeddedElector = createEmbeddedElectorService();
             addIfService(embeddedElector);
           }
         }
       }

6. init方法裏就是一些配置文件的讀取,以及註冊至zk的一些路徑的設置,而內部最重要的就是下面這個new ActiveStandbyElector,這個類就是真正來用來實現主備機切換的,結構體內部創建了一個zkClient。至此,相關服務的初始化方法接介紹完畢了,接下來就要看RM處執行start()後,這幾類相關的服務都做了什麼事情。

   elector = new ActiveStandbyElector(zkQuorum, (int) zkSessionTimeout,
       electionZNode, zkAcls, zkAuths, this, maxRetryNum);

7. 在將RM的start方法之前,我想再講一下RM的初始化方法中的一個重要的方法,這個方法內部new RMActiveServices(this),而RMActiveServices子服務中又添加了許多的子服務,而這些子服務纔是ResourceManager能夠運行起來的重要服務,爲什麼在這裏提及這個,是因爲在HA模式下,RMActiveServices並不是由Yarn自己來控制start的,詳細見後文對RM serviceStart()的分析

   //重要的服務在此方法內部
   createAndInitActiveServices();

Yarn HA -- 集羣的啓動和異常切換

1. 從這個判斷和方法名能看到,如果是HA模式,就將RM設置成standby的狀態,這裏提及一下非HA模式下transitionToActive(),這方法內部就是將RM設置成active狀態,並去啓動初始化方法中RMActiveServices添加的子服務,那接下來我們詳細的看一下HA模式下transitionToStandby(true)內部是怎麼實現的

   if (this.rmContext.isHAEnabled()) {
     transitionToStandby(true);
   } else {
     transitionToActive();
   }

2. 在入口處如果是做了判斷standby模式,則直接返回,這裏我也有點奇怪,服務是剛剛啓動的,爲何會在這裏做這個判斷,猜測是HA的自動故障轉移與啓動是異步進行的,有可能在RM初始化啓動的過程中,自動故障轉移機制已經設置好RM的狀態了,又或者是Yarn的恢復功能會對狀態有存儲,這裏可以記一下,後續分析yarn恢復功能的時候可以一起考慮進去。到這爲止,主備機的RM啓動就返回了,後面就是對其餘一些非RMActiveServices的子服務做啓動,這就不做分析了。那在後文將要分析,Yarn內置的故障轉移是怎麼去設置主備機RM的狀態的。

   synchronized void transitionToStandby(boolean initialize)
       throws Exception {
       //如果已經是standby模式,則返回
     if (rmContext.getHAServiceState() ==
         HAServiceProtocol.HAServiceState.STANDBY) {
       LOG.info("Already in standby state");
       return;
     }
   
     LOG.info("Transitioning to standby state");
     HAServiceState state = rmContext.getHAServiceState();
     rmContext.setHAServiceState(HAServiceProtocol.HAServiceState.STANDBY);
     //如果是active模式,就停掉所有的服務,RM HA的狀態不是由自己啓動過程來控制的
     if (state == HAServiceProtocol.HAServiceState.ACTIVE) {
       stopActiveServices();
       reinitialize(initialize);
     }
     LOG.info("Transitioned to standby state");
   }

3. 上文的分析中,知道了RM的重要服務在HA模式下並沒有去啓動,那這些服務是怎麼啓動起來的呢?這就要從AdminService()作爲入口去分析了,從Yarn HA集羣初始化那一節已經分析過AdminService添加了一個子服務EmbeddedElectorService,這個子服務內部初始化了Yarn HA故障恢復的實例ActiveStandbyElector,那我們來看一下EmbeddedElectorService的start()方法,非常的簡單,elector就是ActiveStandbyElector的實例對象

   protected void serviceStart() throws Exception {
     elector.joinElection(localActiveNodeInfo);
     super.serviceStart();
   }

4. 那我們來看一下這個傳參變量是什麼意思,可以看到是由yarn.resourcemanager.ha.id讀出來的rmId(通常主備機以rm1和rm2命名)和yarn.resourcemanager.cluster-id獲取到的clusterId來構造的節點信息

   String rmId = HAUtil.getRMHAId(conf);
   String clusterId = YarnConfiguration.getClusterId(conf);
   localActiveNodeInfo = createActiveNodeInfo(clusterId, rmId);

5. 接着看  elector.joinElection(localActiveNodeInfo)方法,保存複製了一份appData,然後調用joinElectionInternal()

   public synchronized void joinElection(byte[] data)
       throws HadoopIllegalArgumentException {
   
   //複製一份節點信息的數組
     appData = new byte[data.length];
     System.arraycopy(data, 0, appData, 0, data.length);
   
     LOG.debug("Attempting active election for " + this);
     joinElectionInternal();
   }

6. 在初始化時就創建了一個ZK的客戶端,這裏對ZK客戶端做了一個非空的判斷,最後調用createLockNodeAsync()

   private void joinElectionInternal() {
     Preconditions.checkState(appData != null,
         "trying to join election without any app data");
     if (zkClient == null) {
       if (!reEstablishSession()) {
         fatalError("Failed to reEstablish connection with ZooKeeper");
         return;
       }
     }
   
     createRetryCount = 0;
     wantToBeInElection = true;
     createLockNodeAsync();
   }

7. 可以看到,這個方法內部就是將RM的信息註冊到ZK上 

   private void createLockNodeAsync() {
     zkClient.create(zkLockFilePath, appData, zkAcl, CreateMode.EPHEMERAL,
         this, zkClient);
   }

8. RM註冊到ZK上時,會觸發ZK的回調函數(涉及ZK的機制此處不做詳解),最終調用了一個重要的方法processResult(我刪掉了一些不影響邏輯的註釋和非空的判斷),那我們來看看方法裏都做了些什麼,會從ZK去獲取RM註冊的情況,如果註冊成功的話,就會進入becomeActive(),來做RM節點是否作爲活動機的判斷,如果返回true就調用 monitorActiveStatus();爲這個節點創造監控,未成功就嘗試重新嘗試成爲活動節點,那毋庸置疑,這裏需要分析的重要的方法就是becomeActive()

   @Override
   public synchronized void processResult(int rc, String path, Object ctx,
       String name) {
     Code code = Code.get(rc);
     if (isSuccess(code)) {
       //判斷是否成功成爲active節點
       if (becomeActive()) {
         monitorActiveStatus();
       } else {
         reJoinElectionAfterFailureToBecomeActive();
       }
       return;
     }
   }

9. 直接看關鍵方法appClient.becomeActive();這個方法是回調函數的接口,註冊到ZK必須重寫一個回調函數,這裏由Yarn的故障轉移服務來實現 

   private boolean becomeActive() {
     assert wantToBeInElection;
     if (state == State.ACTIVE) {
       // 如果已經是active則返回
       return true;
     }
     try {
     //對失效的active節點的節點信息做一些校驗和處理
       Stat oldBreadcrumbStat = fenceOldActive();
       writeBreadCrumbNode(oldBreadcrumbStat);
       
       LOG.debug("Becoming active for " + this);
       //將此節點的RM設置成active
       appClient.becomeActive();
       state = State.ACTIVE;
       return true;
     } catch (Exception e) {
       LOG.warn("Exception handling the winning of election", e);
       // Caller will handle quitting and rejoining the election.
       return false;
     }
   }

10. 在EmbeddedElectorService中實現該接口,調用了transitionToActive(req);

   @Override
   public void becomeActive() throws ServiceFailedException {
     try {
       rmContext.getRMAdminService().transitionToActive(req);
     } catch (Exception e) {
       throw new ServiceFailedException("RM could not transition to Active", e);
     }
   }

11. 這個方法內部有三個方法(我刪除了try catch),會重新獲取一些信息,最終在transitionToActive()方法內部啓動RMActiveServices中重要的子服務,成功啓動這些服務後,該節點就會成爲active節點,另外一個節點就會作爲rm的備機

    //故障轉移過來後,需要
    refreshAdminAcls(false);//重新獲取用戶的權限控制
    refreshAll();//重新獲取節點、隊列、用戶等信息
    rm.transitionToActive();//啓動RMActiveServices中重要的子服務

12. 至此Yarn HA集羣的啓動就介紹完畢了,從上文的分析來看,可以得知兩臺RM誰先註冊到ZK就優先成爲active節點,RM的啓動也是由ZK的回調函數來控制的

13. 那當作爲active的節點因異常而down調時,ZK會檢測到這個情況,在創建ZK客戶端後,ZK每隔10s會檢測一次(這意味着不是RM down掉後10s會被檢測到,而是在10s之內),當檢測到這個情況的時候,就會調用另一臺機器的回調函數去切RM爲active狀態

14. 此外,需要補充說明的是,ResourceManager只負責ApplicationMaster的狀態維護和容錯,ApplicationMaster內部管理和調度的任務,比如MapTask和ReduceTask,亦或是spark內部的計算則需要由AppicationMaster自己容錯,這不屬於YARN HA這個系統管理的範疇。再一次證明了Yarn HA的機制是粗粒度的,仍不夠完善。 


小結

總的來看,Yarn HA的故障恢復並不完善,RM的切換會導致正在運行的任務重啓或者重算,並且故障恢復的機制(Yarn 內部自帶的故障恢復)也比較簡陋,從以上的分析可以得知,Yarn只是去解決在另一臺RM down掉的情況下,去將備機的RM拉起來,來有效避免單點故障的問題,卻沒有對正在運行的任務的容錯做有效的保護,這一點可能是Yarn仍需要完善的地方。如果有興趣瞭解Yarn資源調度相關的模塊可以參考我的這篇博文Yarn源碼剖析(零) --- spark任務提交到yarn的流程


作者:蛋撻

日期:2018.10.07

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章