HDFS 2.0 的 HA 實現(xiàn)

- Active NameNode 和 Standby NameNode:兩臺 NameNode 形成互備,一臺處于 Active 狀態(tài),為主 NameNode,另外一臺處于 Standby 狀態(tài),為備 NameNode,只有主 NameNode 才能對外提供讀寫服務。
- 主備切換控制器 ZKFailoverController:ZKFailoverController 作為獨立的進程運行,對 NameNode 的主備切換進行總體控制。ZKFailoverController 能及時檢測到 NameNode 的健康狀況,在主 NameNode 故障時借助 Zookeeper 實現(xiàn)自動的主備選舉和切換,當然 NameNode 目前也支持不依賴于 Zookeeper 的手動主備切換。
- Zookeeper 集群:為主備切換控制器提供主備選舉支持。
- 共享存儲系統(tǒng):共享存儲系統(tǒng)是實現(xiàn) NameNode 的高可用最為關(guān)鍵的部分,共享存儲系統(tǒng)保存了 NameNode 在運行過程中所產(chǎn)生的 HDFS 的元數(shù)據(jù)。主 NameNode 和備NameNode 通過共享存儲系統(tǒng)實現(xiàn)元數(shù)據(jù)同步。在進行主備切換的時候,新的主 NameNode 在確認元數(shù)據(jù)完全同步之后才能繼續(xù)對外提供服務。
- DataNode 節(jié)點:除了通過共享存儲系統(tǒng)共享 HDFS 的元數(shù)據(jù)信息之外,主 NameNode 和備 NameNode 還需要共享 HDFS 的數(shù)據(jù)塊和 DataNode 之間的映射關(guān)系。DataNode 會同時向主 NameNode 和備 NameNode 上報數(shù)據(jù)塊的位置信息。
NameNode 主備切換主要由 ZKFailoverController、HealthMonitor 和 ActiveStandbyElector 這 3 個組件來協(xié)同實現(xiàn):
ZKFailoverController 作為 NameNode 機器上一個獨立的進程啟動 (在 hdfs 啟動腳本之中的進程名為 zkfc),啟動的時候會創(chuàng)建 HealthMonitor 和 ActiveStandbyElector 這兩個主要的內(nèi)部組件,ZKFailoverController 在創(chuàng)建 HealthMonitor 和 ActiveStandbyElector 的同時,也會向 HealthMonitor 和 ActiveStandbyElector 注冊相應的回調(diào)方法。
- HealthMonitor 主要負責檢測 NameNode 的健康狀態(tài),如果檢測到 NameNode 的狀態(tài)發(fā)生變化,會回調(diào) ZKFailoverController 的相應方法進行自動的主備選舉。
- ActiveStandbyElector 主要負責完成自動的主備選舉,內(nèi)部封裝了 Zookeeper 的處理邏輯,一旦 Zookeeper 主備選舉完成,會回調(diào) ZKFailoverController 的相應方法來進行 NameNode 的主備狀態(tài)切換。
NameNode 實現(xiàn)主備切換有以下幾步:
- HealthMonitor 初始化完成之后會啟動內(nèi)部的線程來定時調(diào)用對應 NameNode 的 HAServiceProtocol RPC 接口的方法,對 NameNode 的健康狀態(tài)進行檢測。
- HealthMonitor 如果檢測到 NameNode 的健康狀態(tài)發(fā)生變化,會回調(diào) ZKFailoverController 注冊的相應方法進行處理。
- 如果 ZKFailoverController 判斷需要進行主備切換,會首先使用 ActiveStandbyElector 來進行自動的主備選舉。
- ActiveStandbyElector 與 Zookeeper 進行交互完成自動的主備選舉。
- ActiveStandbyElector 在主備選舉完成后,會回調(diào) ZKFailoverController 的相應方法來通知當前的 NameNode 成為主 NameNode 或備 NameNode。
-
ZKFailoverController 調(diào)用對應 NameNode 的 HAServiceProtocol RPC 接口的方法將 NameNode 轉(zhuǎn)換為 Active 狀態(tài)或 Standby 狀態(tài)。
主備切換
防止腦裂
Zookeeper 在工程實踐的過程中經(jīng)常會發(fā)生的一個現(xiàn)象就是 Zookeeper 客戶端“假死”,所謂的“假死”是指如果 Zookeeper 客戶端機器負載過高或者正在進行 JVM Full GC,那么可能會導致 Zookeeper 客戶端到 Zookeeper 服務端的心跳不能正常發(fā)出,一旦這個時間持續(xù)較長,超過了配置的 Zookeeper Session Timeout 參數(shù)的話,Zookeeper 服務端就會認為客戶端的 session 已經(jīng)過期從而將客戶端的 Session 關(guān)閉?!凹偎馈庇锌赡芤鸱植际较到y(tǒng)常說的雙主或腦裂 (brain-split) 現(xiàn)象。具體到本文所述的 NameNode,假設 NameNode1 當前為 Active 狀態(tài),NameNode2 當前為 Standby 狀態(tài)。如果某一時刻 NameNode1 對應的 ZKFailoverController 進程發(fā)生了“假死”現(xiàn)象,那么 Zookeeper 服務端會認為 NameNode1 掛掉了,根據(jù)前面的主備切換邏輯,NameNode2 會替代 NameNode1 進入 Active 狀態(tài)。但是此時 NameNode1 可能仍然處于 Active 狀態(tài)正常運行,即使隨后 NameNode1 對應的 ZKFailoverController 因為負載下降或者 Full GC 結(jié)束而恢復了正常,感知到自己和 Zookeeper 的 Session 已經(jīng)關(guān)閉,但是由于網(wǎng)絡的延遲以及 CPU 線程調(diào)度的不確定性,仍然有可能會在接下來的一段時間窗口內(nèi) NameNode1 認為自己還是處于 Active 狀態(tài)。這樣 NameNode1 和 NameNode2 都處于 Active 狀態(tài),都可以對外提供服務。這種情況對于 NameNode 這類對數(shù)據(jù)一致性要求非常高的系統(tǒng)來說是災難性的,數(shù)據(jù)會發(fā)生錯亂且無法恢復。Zookeeper 社區(qū)對這種問題的解決方法叫做 fencing,中文翻譯為隔離,也就是想辦法把舊的 Active NameNode 隔離起來,使它不能正常對外提供服務。
ActiveStandbyElector 為了實現(xiàn) fencing,會在成功創(chuàng)建 Zookeeper 節(jié)點
hadoop-ha/${dfs.nameservices}/ActiveStandbyElectorLock 從而成為 Active NameNode 之后,創(chuàng)建另外一個路徑為/hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb 的持久節(jié)點,這個節(jié)點里面保存了這個 Active NameNode 的地址信息。Active NameNode 的 ActiveStandbyElector 在正常的狀態(tài)下關(guān)閉 Zookeeper Session 的時候 (注意由于/hadoop-ha/${dfs.nameservices}/ActiveStandbyElectorLock 是臨時節(jié)點,也會隨之刪除),會一起刪除節(jié)點/hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb。但是如果 ActiveStandbyElector 在異常的狀態(tài)下 Zookeeper Session 關(guān)閉 (比如前述的 Zookeeper 假死),那么由于/hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb 是持久節(jié)點,會一直保留下來。后面當另一個 NameNode 選主成功之后,會注意到上一個 Active NameNode 遺留下來的這個節(jié)點,從而會回調(diào) ZKFailoverController 的方法對舊的 Active NameNode 進行 fencing。
基于 QJM 的共享存儲系統(tǒng)的總體架構(gòu)
基于 QJM 的共享存儲系統(tǒng)主要用于保存 EditLog,并不保存 FSImage 文件。FSImage 文件還是在 NameNode 的本地磁盤上。QJM 共享存儲的基本思想來自于 Paxos 算法,采用多個稱為 JournalNode 的節(jié)點組成的 JournalNode 集群來存儲 EditLog。每個 JournalNode 保存同樣的 EditLog 副本。每次 NameNode 寫 EditLog 的時候,除了向本地磁盤寫入 EditLog 之外,也會并行地向 JournalNode 集群之中的每一個 JournalNode 發(fā)送寫請求,只要大多數(shù) (majority) 的 JournalNode 節(jié)點返回成功就認為向 JournalNode 集群寫入 EditLog 成功。如果有 2N+1 臺 JournalNode,那么根據(jù)大多數(shù)的原則,最多可以容忍有 N 臺 JournalNode 節(jié)點掛掉。
Active NameNode 和 StandbyNameNode 使用 JouranlNode 集群來進行數(shù)據(jù)同步的過程如圖所示,Active NameNode 首先把 EditLog 提交到 JournalNode 集群,然后 Standby NameNode 再從 JournalNode 集群定時同步 EditLog:

數(shù)據(jù)恢復機制分析
處于 Standby 狀態(tài)的 NameNode 轉(zhuǎn)換為 Active 狀態(tài)的時候,有可能上一個 Active NameNode 發(fā)生了異常退出,那么 JournalNode 集群中各個 JournalNode 上的 EditLog 就可能會處于不一致的狀態(tài),所以首先要做的事情就是讓 JournalNode 集群中各個節(jié)點上的 EditLog 恢復為一致。另外如前所述,當前處于 Standby 狀態(tài)的 NameNode 的內(nèi)存中的文件系統(tǒng)鏡像有很大的可能是落后于舊的 Active NameNode 的,所以在 JournalNode 集群中各個節(jié)點上的 EditLog 達成一致之后,接下來要做的事情就是從 JournalNode 集群上補齊落后的 EditLog。只有在這兩步完成之后,當前新的 Active NameNode 才能安全地對外提供服務。
HDFS 2.0 Federation 實現(xiàn)
在 1.0 中,HDFS 的架構(gòu)設計有以下缺點:
- namespace 擴展性差:在單一的 NN 情況下,因為所有 namespace 數(shù)據(jù)都需要加載到內(nèi)存,所以物理機內(nèi)存的大小限制了整個 HDFS 能夠容納文件的最大個數(shù)(namespace 指的是 HDFS 中樹形目錄和文件結(jié)構(gòu)以及文件對應的 block 信息);
- 性能可擴展性差:由于所有請求都需要經(jīng)過 NN,單一 NN 導致所有請求都由一臺機器進行處理,很容易達到單臺機器的吞吐;
- 隔離性差:多租戶的情況下,單一 NN 的架構(gòu)無法在租戶間進行隔離,會造成不可避免的相互影響。
而 Federation 的設計就是為了解決這些問題,采用 Federation 的最主要原因是設計實現(xiàn)簡單,而且還能解決問題。
Federation 的核心設計思想
Federation 的核心思想是將一個大的 namespace 劃分多個子 namespace,并且每個 namespace 分別由單獨的 NameNode 負責,這些 NameNode 之間互相獨立,不會影響,不需要做任何協(xié)調(diào)工作(其實跟拆集群有一些相似),集群的所有 DataNode 會被多個 NameNode 共享。
其中,每個子 namespace 和 DataNode 之間會由數(shù)據(jù)塊管理層作為中介建立映射關(guān)系。
