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

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