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關鍵字


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM