• HDFS ZKFC自动切换原理分析


    前言


    Hadoop社区在HDFS-1623High Availability Framework for HDFS NN)以及相关JIRA中对NameNode增加了高可用性的支持。但是它的实现需要依赖管理员手动切换NameNode,以此来触发NameNode的切换。这种操作方式有时会带来一些问题,比如说一个NameNode因为非主观原因导致异常,挂了,这个时候怎么办,这个时候我们可能更需要自动切换的一套机制来解决这个问题。毕竟集群管理员不会随时随地地监控着集群。基于这个应用场景,社区在HDFS-3042Automatic failover support for NN HA)对现有HA的功能增加自动切换的支持。因为HDFS-3042是一个父JIRA,本文我们将主要关注其最后的核心子JIRA,HDFS-2185HA: HDFS portion of ZK-based FailoverController),也就是基于ZK的自动切换原理实现,对应呈现的形式就是我们平常看到的ZKFC进程。

    基于ZK的HA切换原理


    在讲解ZKFC进程的组成部分之前,我们需要了解HDFS如何依赖ZK实现切换操作的。首先我们需要了解一下什么是ZK以及ZK有什么作用,然后我们才能理解HDFS为什么要利用ZK来实现自动切换的机制。

    ZK全称是Zookeeper,ZK的一个很大的特点是它可以保持高度的一致性,而且它本身可以支持HA,在ZK集群最后,只要保证半数以上节点存活,ZK集群就还能对外提供服务。

    那么HDFS的Active、Standby节点与ZK有什么关联呢?

    当一个NameNode被成功切换为Active状态时,它会在ZK内部创建一个临时的znode,在znode中将会保留当前Active NameNode的一些信息,比如主机名等等。当Active NameNode出现失败或连接超时的情况下,监控程序会将ZK上对应的临时znode进行删除,znode的删除事件会主动触发到下一次的Active NamNode的选择。

    因为ZK是具有高度一致性的,它能保证当前最多只能有一个节点能够成功创建znode,成为当前的Active Name。这也就是为什么社区会利用ZK来做HDFS HA的自动切换的原因。

    HDFS HA自动切换机制的核心:ZKFC


    正如本节小标图所显示的,HDFS HA自动切换机制的核心对象是ZKFC ,也就是我们平常在NameNode节点上会启动的ZKFC进程。

    在ZKFC的进程内部,运行着3个对象服务:

    • HealthMonitor:监控NameNode是否不可用或是进入了一个不健康的状态。
    • ActiveStandbyElector:控制和监控ZK上的节点的状态。
    • ZKFailoverController:协调HealMonitor和ActiveStandbyElector对象,处理它们发来的event变化事件,完成自动切换的过程。

    以上3者的运行结果图如图1-1所示。



    图 1-1 ZKFC组成以及运行图

    接下来我们来简单介绍这3个对象服务。

    HealthMonitor


    首先是HealthMonitor监控服务,通过它的名称我们就能够看出它是一个监控服务。在此对象内部,定义了5种服务状态,如下代码所示:

      public enum State {
        // 1.The health monitor is still starting up.
        INITIALIZING,
    
        // 2.The service is not responding to health check RPCs.
        SERVICE_NOT_RESPONDING,
    
        // 3.The service is connected and healthy.
        SERVICE_HEALTHY,
    
        // 4.The service is running but unhealthy.
        SERVICE_UNHEALTHY,
    
        // 5.The health monitor itself failed unrecoverably and can
        // no longer provide accurate information.
        HEALTH_MONITOR_FAILED;
      }

    翻译过来就是下面5种状态:

    • 1.HealMonitor初始化启动状态。
    • 2.健康检查无响应状态。
    • 3.服务检测健康状态。
    • 4.服务检查不健康状态。
    • 5.监控服务本身失败不可用状态。

    上述状态最后除去1和5的特殊情况,2、3和4的情况是HealMonitor服务队NameNode的正常检测状况。HealMonitor对象检测NameNode的健康状况的逻辑其实非常简单:发送一个RPC请求,查看是否有响应。相关代码如下:

      private class MonitorDaemon extends Daemon {
        private MonitorDaemon() {
          super();
          ...
        }
    
        @Override
        public void run() {
          // 循环监控操作
          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 {
            status = proxy.getServiceStatus();
            // 调用一次监控对象的RPC方法
            proxy.monitorHealth();
            healthy = true;
          } catch (Throwable t) {
            // 如果出现异常情况
            if (isHealthCheckFailedException(t)) {
              ...
              // 进入服务不健康状态
              enterState(State.SERVICE_UNHEALTHY);
            } else {
              ...
              // 进入服务无响应状态
              enterState(State.SERVICE_NOT_RESPONDING);
              Thread.sleep(sleepAfterDisconnectMillis);
              return;
            }
          }
          ...
          // 如果都正常,则是服务健康状态
          if (healthy) {
            enterState(State.SERVICE_HEALTHY);
          }
          // 进行检测间隔时间的睡眠
          Thread.sleep(checkIntervalMillis);
        }
      }

    在这里我们检测出不同状态之后,会调用enterState方法,在这个方法内部会触发相应状态的回调事件。这些事件会在ZKFailoverController类中被处理。

    ActiveStandbyElector


    接下来是ActiveStandbyElector对象,ActiveStandbyElector对象主要负责的是与Zookeeper之间的交互操作。比如一个节点成功被切换为Active Name,ActiveStandbyElector对象会在ZK上创建一个节点。在这个类最后,有2个涉及到Active Name选举的关键方法:joinElection()和quitElection()方法

    joinElection方法被调用表明本地的NameNode准备参与Active NameNode的选举,为一个备选节点。quitElection方法被调用表示的是本地节点退出本次的选举。

    这2个方法会在HDFS HA自动切换最后被调用。显然quitElection方法会在原Active NameNode所在节点中被调用。

    在joinElection参加选举的方法中,会执行在ZK上创建临时znode的方法,代码如下:

      private void joinElectionInternal() {
        ...
        createRetryCount = 0;
        wantToBeInElection = true;
        createLockNodeAsync();
      }

    creareLockNodeAsync方法如下:

      private void createLockNodeAsync() {
        // 调用zk客户端方法创建临时znode
        zkClient.create(zkLockFilePath, appData, zkAcl, CreateMode.EPHEMERAL,
            this, zkClient);
      }

    在quitElection退出选举之后,这个临时节点会被删除,相关代码如下:

      public synchronized void quitElection(boolean needFence) {
        LOG.info("Yielding from election");
        if (!needFence && state == State.ACTIVE) {
          // 如果当前NameNode从Active状态变为Standby状态,则删除临时znode
          tryDeleteOwnBreadCrumbNode();
        }
        reset();
        wantToBeInElection = false;
      }

    当然,在ActiveStandbyElector类中还有其它涉及ZK操作的方法,大家可以自行阅读此对象进行更加深入地学习。

    ZKFailoverController


    最后是ZKFailoverController控制器类,这个对象类包含并协调以上2个对象类的操作。首先来看此对象类的定义,代码如下:

    public abstract class ZKFailoverController {
      ...
      // 服务目标对象
      protected final HAServiceTarget localTarget;
      // NameNode健康监控服务
      private HealthMonitor healthMonitor;
      // ZK交互协调器
      private ActiveStandbyElector elector;
      protected ZKFCRpcServer rpcServer;
      ...

    然后我们再来看HealthMonitor 、ActiveStandbyElector 的初始化。

    首先是HealthMonitor 对象,

      private void initHM() {
        // HealthMonitor对象的初始化
        healthMonitor = new HealthMonitor(conf, localTarget);
        // 加入回调操作对象,以此不同的状态变化可以触发这些回调的执行
        healthMonitor.addCallback(new HealthCallbacks());
        healthMonitor.addServiceStateCallback(new ServiceStateCallBacks());
        healthMonitor.start();
      }

    在之前分析的HealthMonitor类中的enterState方法中会触发这些回调的执行,我们以ServiceStateCallBacks回调为例,来看看里面具体会执行哪些操作。

      class ServiceStateCallBacks implements HealthMonitor.ServiceStateCallback {
        @Override
        public void reportServiceStatus(HAServiceStatus status) {
          // 传入当前检测出的健康状态进行检查
          verifyChangedServiceState(status.getState());
        }
      }

    继续进入此方法,

     void verifyChangedServiceState(HAServiceState changedState) {
        synchronized (elector) {
          synchronized (this) {
            // 如果服务状态为初始化状态
            if (serviceState == HAServiceState.INITIALIZING) {
              if (quitElectionOnBadState) {
                LOG.debug("rechecking for electability from bad state");
                recheckElectability();
              }
              // 重新检测方法完毕并返回
              return;
            }
            ...
            // 因为当前报告的状态与期望状态不匹配,退出选举
            LOG.error("Local service " + localTarget
                + " has changed the serviceState to " + changedState
                + ". Expected was " + serviceState
                + ". Quitting election marking fencing necessary.");
            delayJoiningUntilNanotime = System.nanoTime()
                + TimeUnit.MILLISECONDS.toNanos(1000);
            elector.quitElection(true);
            quitElectionOnBadState = true;
            ...
          }
        }
      }

    在recheckElectability方法中,会根据最近一次检测出的健康状态,做对应的处理动作,代码如下:

      private void recheckElectability() {
        // Maintain lock ordering of elector -> ZKFC
        synchronized (elector) {
          synchronized (this) {
            boolean healthy = lastHealthState == State.SERVICE_HEALTHY;
    
            ...
            // 根据最近一次的健康状态,调用ActiveStandbyElector的相关方法
            switch (lastHealthState) {
            case SERVICE_HEALTHY:
              // 如果当前状态为健康,则加入此轮选举
              elector.joinElection(targetToData(localTarget));
              if (quitElectionOnBadState) {
                quitElectionOnBadState = false;
              }
              break;
    
            case INITIALIZING:
              LOG.info("Ensuring that " + localTarget + " does not " +
                  "participate in active master election");
              // 如果当前处于初始化状态,则暂时不加入选举
              elector.quitElection(false);
              serviceState = HAServiceState.INITIALIZING;
              break;
    
            case SERVICE_UNHEALTHY:
            case SERVICE_NOT_RESPONDING:
              // 如果当前状态为不健康或无响应状态,则退出选择
              LOG.info("Quitting master election for " + localTarget +
                  " and marking that fencing is necessary");
              elector.quitElection(true);
              serviceState = HAServiceState.INITIALIZING;
              break;
    
            case HEALTH_MONITOR_FAILED:
              fatalError("Health monitor failed!");
              break;
    
            default:
              throw new IllegalArgumentException("Unhandled state:"
                                                   + lastHealthState);
            }
          }
        }
      }

    在上面的操作中,就调用到了ActiveStandbyElector的内部方法。在这里我们可以看到,当当前NameNode的健康检测状态为SERVICE_UNHEALTHY和SERVICE_NOT_RESPONDING时,会触发到quitElection方法,从而使自身暂时退出Active状态。

    说了这么多关于ZKFailoverController的内容,可能有人会感觉奇怪,我们启动的ZKFC进程好像并不叫这个名称啊?没错,在NameNode,我们实际启动的是它的一个继承子类:DFSZKFailoverController。此类的绝大多数方法走的还是其父类的方法。

    综上所述,ZKFC类对象内部的结构关系图如图1-2所示。



    图 1-2 ZKFC内部结构图

    OK,本文所要阐述的内容就是如此,更多关于HDFS ZKFC自动切换原理的细节内容可以点击文章末尾的链接进行学习。

    参考资料


    [1].High Availability Framework for HDFS NN
    [2].Automatic failover support for NN HA
    [3].HA: HDFS portion of ZK-based FailoverController
    [4].zkfc-design

  • 相关阅读:
    深度讲解Go语言-学习笔记
    vagrant常用命令
    CentOS7 安装Python虚拟环境 virtualenvwrapper
    《Android开发艺术探索》读书笔记——Cha3.2.3改变布局参数实现View的滑动
    Map接口的实现类 Map的区别
    Java多线程之内存可见性和原子性操作 一 synchronized
    LeetCode 153. Find Minimum in Rotated Sorted Array
    LeetCode 64. Minimum Path Sum
    实现线程同步的几种方式
    IOC的底层原理
  • 原文地址:https://www.cnblogs.com/bianqi/p/12183712.html
Copyright © 2020-2023  润新知