HDFS HA 原理
標簽:HDFS HA
概述
在 Hadoop 2.x 版本中,Hadoop 實現了 HDFS 的 HA 功能,從而解決了 HDFS 中 NameNode 單點問題。
在 HDFS NameNode 的高可用架構中涉及到了多個部分,主要包括:Active NameNode、Standby NameNode、ZKFailoverController、ZooKeeper集群和共享存儲系統(tǒng)。其中,Active NameNode 和 Standby NameNode 互為主備,通過共享存儲系統(tǒng)共享同一份元數據,由 ZooKeeper 集群負責選取主 NameNode。ZKFailoverController 負責監(jiān)控 NameNode 的狀態(tài),當主 NameNode 出現故障后,進行主備切換。
下面就對 HDFS 的 HA 做進一步的介紹。
共享存儲系統(tǒng)
共享存儲系統(tǒng)可以說是 HDFS HA 中最重要的一個部分,因為主備 NameNode 需要通過共享存儲系統(tǒng)進行元數據的實時同步,這樣才能保證在進行主備切換的時候,備 NameNode 上的元數據和主 NameNode 上的是一致的,確保切換之后不會丟失數據。
在 Hadoop 社區(qū)中曾經出現過多種 NameNode 共享存儲解決方案,但現在 Hadoop 中默認的實現是基于 QJM(Quorum Journal Manager),因此我們這里只對 QJM 的實現方式進行介紹。
NameNode 元數據
在介紹具體的 QJM 實現之前,先對 NameNode 的元數據文件做一個簡單的介紹。
NameNode 的元數據文件中最主要的是兩類文件:Fsimage 和 EditLog,這兩種文件一起構成了 NameNode 內存中的文件系統(tǒng)鏡像。
FSImage
FSImage 是 NameNode 內存中文件系統(tǒng)鏡像的一個快照,在 NameNode 啟動的時候,會先把 FSImage 加載到內存中形成文件系統(tǒng)鏡像。FSImage 是由 NameNode 生成,保存在本地磁盤上,文件名形如 fsimage_${end_txid},其中 ${end_txid} 表示這個 fsimage 文件的結束事務 id。
EditLog
EditLog 中保存了客戶端對 HDFS 系統(tǒng)的所有更新操作,記錄在 EditLog 中的每個操作稱為一個事務,每個事務都有一個整數形式的事務 id 作為編號。
EditLog 文件會被分割為很多段,每一段稱為一個 Segment,而 Segment 可以分為兩種:一種是處于正在寫入狀態(tài)的,文件名形如 edits_inprogress_${start_txid}
,其中 ${start_txid}
表示這個 Segment 的起始事務 id;另一種是處于寫入完成狀態(tài)的,文件名形如 edits_${start_txid}-${end_txid}
,其中 ${start_txid}
表示起始事務 id,${end_txid}
表示結束事務 id。
在介紹完了 NameNode 的元數據文件之后,現在來介紹 NameNode 是如何通過 QJM 實現元數據共享的。
元數據共享
HDFS 是通過 QJM 來實現的 NameNode 元數據的共享,但需要注意的是,QJM 上只保存了 EditLog 文件,FSImage 文件還是保存在 NameNode 的本地磁盤上。
QJM 系統(tǒng)的基本思想是來源于 Paxos 算法,由多個被稱為 Journal Node 的節(jié)點組成,每個 Journal Node 上都保存了相同的數據。當向 QJM 中寫入數據時,只有向超過半數的 Journal Node 上都寫入成功時才認為寫入成功。
每當客戶端通過 Active NameNode 對 HDFS 進行更新操作時,Active NameNode 都會將此次操作同時寫入本地磁盤和 QJM 上的 EditLog 文件。而 Standby NameNode 則會定時從 QJM 上獲取 EditLog 文件,然后把同步的 EditLog 放到內存中的文件系統(tǒng)鏡像中,以保持自身內存中的元數據跟 Active NameNode 同步。
如果 Active NameNode 向 QJM 寫入 EditLog 失敗(也就是沒有滿足超過半數的 Journal Node 返回成功),那么 Active NameNode 會退出進程,然后等待 Standby NameNode 接管后進行數據恢復。
Standby NameNode 除了定時從 QJM 同步 EditLog 之外,還會定期對內存中的文件系統(tǒng)鏡像做 checkpoint,然后將生成的 FSImage 文件傳給 Active NameNode。Active NameNode 收到 Standby NameNode 發(fā)送的 FSImage 之后,會將本地舊的 FSImage 文件刪除。
這樣,通過 QJM,主備 NameNode 實現了元數據的同步,那么當主備進行切換的時候,他們之間又是如何進行交互的呢?
主備切換
當 Active NameNode 出現故障異常退出的時候,需要保證在 Standby NameNode 切換成 Active 狀態(tài)后元數據與 Active NameNode 保持一致。但是從之前的介紹中可以得知,Standby NameNode 是定時從 QJM 上獲取 EditLog 文件,那么當 Active NameNode 出現故障時,Standby NameNode 上的數據并不是嚴格同步的。
同時,當 Active NameNode 出現故障時,也無法保證 QJM 上的數據是一致的。考慮這樣一種情況,假設 QJM 中有 3 個節(jié)點,Active NameNode 在分別向這三個節(jié)點發(fā)送完寫入命令后崩潰退出了,此時只有一個 Journal Node 寫入成功,而其他兩個 Journal Node 寫入失敗。那么此時 QJM 就處于一個數據不一致的狀態(tài),在這種情況下就需要對 QJM 上的數據進行恢復,使其達到一致性狀態(tài)。
綜上所述,在發(fā)生主備切換 Standby NameNode 切換為 Active 狀態(tài)后,需要先對 QJM 進行數據恢復,然后進行數據同步,只有執(zhí)行完這兩個操作之后,Standby NameNode 才能對外提供服務。
下面對這兩個過程做一個簡單的介紹。
QJM 的數據恢復
首先,需要明確的是,數據恢復肯定是發(fā)生在 Standby NameNode 成為 Active 之后。那么在新的 Active NameNode 被選舉出來之后,需要更新所有 Journal Node 上的 Epoch(可以將 Epoch 理解成每一次主備切換的標識符,是一個全局唯一的順序遞增的整數)。
其次,需要進行數據恢復的肯定是 QJM 上最后一個 EditLog Segment 文件,因為 Active NameNode 在異常退出前寫入的肯定是最新的 EditLog 文件。每個 Journal Node 上最后的 EditLog Segment 的起始事務 id 會在更新完 Epoch 后返回給 Active NameNode,而 Active NameNode 則是從所有 Journal Node 返回的 Epoch 中選取最大的一個作為數據恢復的基準數據源。
Active NameNode 將數據恢復的基準數據源所在的 Journal Node 信息發(fā)送給其他所有 Journal Node 之后,其他的節(jié)點會從該數據源所在節(jié)點上下載 EditLog Segment 文件,將本地的文件替換掉。
數據同步
在完成了數據恢復的過程后,此時 QJM 上的數據已經達到了一致狀態(tài),而且與 Active NameNode 退出前的數據一致。此時新的 Active NameNode 只需要將 QJM 上未同步的 EditLog 文件同步到本地,加載到內存中后就可以達到與舊的 Active NameNode 完全一致的元數據狀態(tài)。
而新的 Active NameNode 從 QJM 上同步數據的過程和 Standby NameNode 定時從 QJM 上同步 EditLog 的實現是一樣的。
主備切換實現
在介紹了共享存儲系統(tǒng)之后,下面就可以來介紹主備 NameNode 在發(fā)生故障切換時的具體過程。
NameNode 的主備切換主要由 ZKFailoverController、HealthMonitor 和 ActiveStandbyElector 這三個組件協(xié)同實現。
首先簡單介紹下這三個組件的作用。
ZKFailoverController
ZKFailoverController 作為 NameNode 上的一個獨立進程啟動(zkfc 進程),啟動的時候會創(chuàng)建 HealthMonitor 和 ActiveStandbyElector 這兩個線程。ZKFailoverController 在創(chuàng)建了這兩個線程的時候,同時也向 HealthMonitor 和 ActiveStandbyElector 注冊了相應的回調方法。
HeathMonitor
HealthMonitor 主要負責監(jiān)測 NameNode 的健康狀態(tài),如果發(fā)現 NameNode 的狀態(tài)發(fā)生了變化,那么會調用 ZKFailoverController 的回調方法進行自動的主備選舉。
ActiveStandbyElector
ActiveStandbyElector 主要負責進行自動的主備選舉,內部封裝了 ZooKeeper 的處理邏輯。一旦 ZooKeeper 上對 NameNode 的主備選舉完成,ActiveStandbyElector 會調用 ZKFailoverController 的回調方法來進行 NameNode 的主備切換。
在介紹完了這三個組件之后,就可以來介紹具體的主備切換過程了。
具體過程
NameNode 的主備切換過程大體過程如下:
- HealthMonitor 在初始化完成后,會啟動內部的線程來定時監(jiān)測 NameNode 的健康狀態(tài)。
- HealthMonitor 如果發(fā)現 NameNode 的狀態(tài)發(fā)生了改變,則會回調 ZKFailoverController 注冊的方法進行處理。
- 如果 ZKFailoverController 判斷需要進行主備切換,則會使用 ActiveStandbyElector 來進行自動的主備選舉。
- ActiveStandbyElector 與 ZooKeeper 進行交互,完成自動的主備選舉。
- ActiveStandbyElector 在完成主備選舉后,會回調 ZKFailoverController 注冊的方法,通知 ZKFailoverController 當前的 NameNode 成為主或備。
- ZKFailoverController 在接收到 ActiveStandbyElector 的通知后調用對應 NameNode 的接口,將 NameNode 轉換為 Active 或 Standby 狀態(tài)。