Zookeeper实现HDFS高可用

接下来,记录下Zookeeper在Hadoop HA中相关的作用,部分内容参考文末博文。

HDFS高可用

Zookeeper的一个重要的应用就是实现Hadoop集群的高可用,在Hadoop 1.x版本中只有一个NameNode来负责整个集群的元数据管理,以及与client的交互,如果这个唯一的NameNode宕机,会出现单点故障,无法对外提供服务。

到了Hadoop 2.0版本,出现了HA高可用解决方案,如下图所示会有两个NameNode,一台为主另一台为backup用。其中处于standby备用状态的NameNode也会接收DataNode的block report,它上面的元数据也需要和active状态NameNode的元数据保持一致,其中元数据同步是通过JournalNode来实现的,为了防止同步不可用,JournalNode也会配置成集群。

下图为两个NameNode竞争active的大致过程,竞争失败的会变成standby状态。

  1. ZKFC进程启动后,会初始化HealthMonitor和ActiveStandbyElector服务,其中HealthMonitor会监控NameNode健康状况。
  2. HealthMonitor会将监控健康状态结果反馈给ZKFailoverController。
  3. ZKFailoverController会调用ActiveStandbyElector服务进行主备选举。
  4. ActiveStandbyElector是通过在Zookeeper中创建临时节点是否抢先,来决定是否发起的NameNode就是active,由于Zookeeper的写一致性,只有保证其中一个创建临时节点成功,另外一个将失败。
  5. 如果下图中左边的成功,会在Zookeeper集群中创建两个节点,一个为临时节点(ActiveStandbyElectorLock),一个为永久节点(ActiveBreadCrumb),其中临时节点创建成功与否将决定谁是active,而永久节点是为了防止active状态的NameNode非正常退出后再恢复导致的双主脑裂问题,选举的结果会返回到ActiveStandbyElector。
  6. ActiveStandbyElector服务将主备状态返回到ZKFailoverController。
  7. ZKFailoverController会将选举成功的NameNode切换为active,失败则为standby状态。

故障恢复主要组件

为了保证active NameNode宕机后,standby→active自动切换实现故障恢复,需要依靠如下几个组件。

  1. HealthMonitor对象:监控NameNode是否"不可用"或进入"不健康"状态。
  2. ActiveStandbyElector对象:控制和监控ZK上的节点的状态,一个永久节点一个临时节点。
  3. ZKFailoverController对象:订阅HealMonitor和ActiveStandbyElector对象发来的event事件,管理NameNode的状态,当NameNode不能提供服务还会负责对它进行隔离。

上面三个组件运行在各自NameNode的JVM中,上图中有两个NameNode,因此运行在两个不同NameNode的JVM中,这三个一起组成一个ZKFC进程。

HealthMonitor

HealthMonitor会定时调用NameNode的HAServiceProtocol RPC接口的monitorHealth和getServiceStatus方法,监控NameNode的健康状态并向ZKFC反馈。具体在doHealthChecks方法中会调用,但是在执行doHealthChecks方法之前,会先执行MonitorDaemon方法,给NameNode发送RPC请求。

MonitorDaemon方法。

private class MonitorDaemon extends Daemon {
    private MonitorDaemon() {
      super();
      setName("Health Monitor for " + targetToMonitor);
      setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
          LOG.fatal("Health monitor failed", e);
          enterState(HealthMonitor.State.HEALTH_MONITOR_FAILED);
        }
      });
    }
    
    @Override
    public void run() {
      //循环监控
      //shouldRun是使用volatile关键字修饰的,初始值是true
      while (shouldRun) {
        try { 
          //循环连接直到成功
          loopUntilConnected();
          //执行一次健康检测
          doHealthChecks();
        } catch (InterruptedException ie) {
          Preconditions.checkState(!shouldRun,
              "Interrupted but still supposed to run");
        }
      }
    }
  }

doHealthChecks方法。

 private void doHealthChecks() throws InterruptedException {
    while (shouldRun) {
      HAServiceStatus status = null;
      boolean healthy = false;
      try {
        //调用HAServiceProtocol RPC接口的方法
        status = proxy.getServiceStatus();
        proxy.monitorHealth();
        healthy = true;
      } catch (Throwable t) {
        if (isHealthCheckFailedException(t)) {
          LOG.warn("Service health check failed for " + targetToMonitor
              + ": " + t.getMessage());
          //进入服务不健康State
          enterState(State.SERVICE_UNHEALTHY);
        } else {
          LOG.warn("Transport-level exception trying to monitor health of " +
              targetToMonitor + ": " + t.getCause() + " " + t.getLocalizedMessage());
          RPC.stopProxy(proxy);
          proxy = null;
          //进入服务无响应state
          enterState(State.SERVICE_NOT_RESPONDING);
          Thread.sleep(sleepAfterDisconnectMillis);
          return;
        }
      }
      
      if (status != null) {
        setLastServiceStatus(status);
      }
      if (healthy) {
        //如果正常,进入健康state
        enterState(State.SERVICE_HEALTHY);
      }

      //线程睡眠一段时间,间隔为ha.health-monitor.check-interval.ms中设置的时间
      Thread.sleep(checkIntervalMillis);
    }
  }

描述服务状态使用枚举类型表示,分为5种,常见的就是中间三种,分别为未响应、健康和不健康。

public enum State {
    /**
     * The health monitor is still starting up.
     */
    INITIALIZING,

    /**
     * The service is not responding to health check RPCs.
     */
    SERVICE_NOT_RESPONDING,

    /**
     * The service is connected and healthy.
     */
    SERVICE_HEALTHY,
    
    /**
     * The service is running but unhealthy.
     */
    SERVICE_UNHEALTHY,
    
    /**
     * The health monitor itself failed unrecoverably and can
     * no longer provide accurate information.
     */
    HEALTH_MONITOR_FAILED;
  }

ActiveStandbyElector

通过ActiveStandbyElector,可以实现对NameNode的主备选举,当发起一次主备选举时,在Zookeeper上会尝试创建临时节点/hadoop-ha/${dfs.nameservices}/ActiveStandbyElectorLock,Zookeeper的写一致性保证最终只会有一个znode创建成功。其中dfs.nameservices是自定义的HA服务名,它是逻辑上的服务名,用户不需要关心具体提供的namenode是哪个,只需要连接它就可以享受服务。

如下图所示,就创建了临时znode,其中ns是HA服务名,可以在hdfs-site.xml中定义。除了ActiveStandbyElectorLock,还有另外一个znode,这个节点是持久节点,主要为隔离提供判断依据,防止脑裂,后面会说明。

创建上述临时znode成功所对应的NameNode,就会成为active,即主机名为hadoop01上的NameNode会成为active,而创建失败的则会成为standby。如下图显示hadoop01的状态即为active,说明选主成功。

不管选举成功与否,所有ActiveStandbyElector都会在Zookeeper注册一个Watcher来监听这个临时节点的状态变化,如果active NameNode对应的HealthMonitor检测到NameNode状态异常时,会删除在Zookeeper上创建的临时节点ActiveStandbyElectorLock。然后standby NameNode的ActiveStandbyElector注册的Watcher就会收到这个节点的NodeDeleted事件,并会再次创建ActiveStandbyElectorLock,如果成功,则standby NameNode将被选举为active。

以上过程主要通过ActiveStandbyElector的如下几个关键方法实现的,具体方法内容不再记录,可根据此图提示进行查看理解,但是本人水平有限不一定理解准确。

ZKFailoverController

ZKFailoverController类在org.apache.hadoop.ha包下,启动后会初始化HealthMonitor和ActiveStandbyElector对象。

从源码可以看出,ZKFailoverController有两个重要的内部类,一个是ElectorCallbacks,一个是HealthCallbacks,分别负责active节点选举和NameNode的健康监控用。

隔离机制

由于存在双NameNode,会存在两个NameNode都为active的可能,这种情况是不允许发生的,此时上文中提到的ActiveBreadCrumb永久节点就登场了。ActiveBreadCrumb是一个永久znode,如果active NameNode正常退出,这个永久节点和临时节点ActiveStandbyElectorLock都会被删除,但是很多时候情况不是那么乐观,这个永久节点会保留下来。当下个一新的候选NameNode在成功创建新的ActiveStandbyElectorLock后,不会马上切换成active,还需要通过ActiveBreadCrumb获取上一个active NameNode的信息,然后尝试调用老active NameNode的HAServiceProtocol RPC接口的transitionToStandby方法将其切换为standby。如果成功切换那候选NameNode才切换为新的active,否则就会进入Hadoop自带的隔离机制,有sshfence和shellfence两种。

  1. sshfence:通过ssh远程连接到上一个NameNode,然后执行kill -9命令将zkfc进程强制杀死。
  2. shellfence:运行一个shell命令将active NameNode隔离。

JournalNode集群

共享存储系统,负责存储HDFS的元数据,Active NameNode写入,Standby NameNode读出,实现元数据同步。在主备切换过程中,新的Active NameNode必须确保元数据和老Active NameNode同步完成,才能对外提供服务。

以上,理解不一定正确并且有限,学习就是一个不断认识和纠错的过程。

参考博文:

(1)https://segmentfault.com/a/1190000007239743 dfs.nameservices

(2)https://www.cnblogs.com/youngchaolin/p/12113127.html hadoop完全分布式

(3)https://blog.csdn.net/SmallCatBaby/article/details/89934380 ZKFC

(4)https://issues.apache.org/jira/browse/HDFS-2185 ZKFC design

(5)https://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html volatile关键字

posted @ 2020-06-08 23:36  斐波那切  阅读(2600)  评论(2编辑  收藏  举报