接下來,記錄下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狀態。
- ZKFC進程啟動后,會初始化HealthMonitor和ActiveStandbyElector服務,其中HealthMonitor會監控NameNode健康狀況。
- HealthMonitor會將監控健康狀態結果反饋給ZKFailoverController。
- ZKFailoverController會調用ActiveStandbyElector服務進行主備選舉。
- ActiveStandbyElector是通過在Zookeeper中創建臨時節點是否搶先,來決定是否發起的NameNode就是active,由於Zookeeper的寫一致性,只有保證其中一個創建臨時節點成功,另外一個將失敗。
- 如果下圖中左邊的成功,會在Zookeeper集群中創建兩個節點,一個為臨時節點(ActiveStandbyElectorLock),一個為永久節點(ActiveBreadCrumb),其中臨時節點創建成功與否將決定誰是active,而永久節點是為了防止active狀態的NameNode非正常退出后再恢復導致的雙主腦裂問題,選舉的結果會返回到ActiveStandbyElector。
- ActiveStandbyElector服務將主備狀態返回到ZKFailoverController。
- ZKFailoverController會將選舉成功的NameNode切換為active,失敗則為standby狀態。
故障恢復主要組件
為了保證active NameNode宕機后,standby→active自動切換實現故障恢復,需要依靠如下幾個組件。
- HealthMonitor對象:監控NameNode是否"不可用"或進入"不健康"狀態。
- ActiveStandbyElector對象:控制和監控ZK上的節點的狀態,一個永久節點一個臨時節點。
- 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兩種。
- sshfence:通過ssh遠程連接到上一個NameNode,然后執行kill -9命令將zkfc進程強制殺死。
- 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關鍵字