HDFS是Hadoop Distribute File System 的簡(jiǎn)稱,也就是Hadoop的一個(gè)分布式文件系統(tǒng)。它是hadoop生態(tài)的核心,自己在使用中也遇到了一些問(wèn)題。
學(xué)習(xí)整理:
-
請(qǐng)簡(jiǎn)述HDFS的HA.
HDFS NameNode 的高可用整體架構(gòu)如圖:
NameNode 的高可用架構(gòu)主要分為下面幾個(gè)部分:
- Active NameNode 和 Standby NameNode:兩臺(tái) NameNode 形成互備,一臺(tái)處于 Active 狀態(tài),為主 NameNode,另外一臺(tái)處于 Standby 狀態(tài),為備 NameNode,只有主 NameNode 才能對(duì)外提供讀寫服務(wù)。
- 主備切換控制器 ZKFailoverController:ZKFailoverController 作為獨(dú)立的進(jìn)程運(yùn)行,對(duì) NameNode 的主備切換進(jìn)行總體控制。ZKFailoverController 能及時(shí)檢測(cè)到 NameNode 的健康狀況,在主 NameNode 故障時(shí)借助 Zookeeper 實(shí)現(xiàn)自動(dòng)的主備選舉和切換,當(dāng)然 NameNode 目前也支持不依賴于 Zookeeper 的手動(dòng)主備切換。
- Zookeeper 集群:為主備切換控制器提供主備選舉支持。
- 共享存儲(chǔ)系統(tǒng):共享存儲(chǔ)系統(tǒng)是實(shí)現(xiàn) NameNode 的高可用最為關(guān)鍵的部分,共享存儲(chǔ)系統(tǒng)保存了 NameNode 在運(yùn)行過(guò)程中所產(chǎn)生的 HDFS 的元數(shù)據(jù)。主 NameNode 和
- NameNode 通過(guò)共享存儲(chǔ)系統(tǒng)實(shí)現(xiàn)元數(shù)據(jù)同步。在進(jìn)行主備切換的時(shí)候,新的主 NameNode 在確認(rèn)元數(shù)據(jù)完全同步之后才能繼續(xù)對(duì)外提供服務(wù)。
- DataNode 節(jié)點(diǎn):除了通過(guò)共享存儲(chǔ)系統(tǒng)共享 HDFS 的元數(shù)據(jù)信息之外,主 NameNode 和備 NameNode 還需要共享 HDFS 的數(shù)據(jù)塊和 DataNode 之間的映射關(guān)系。DataNode 會(huì)同時(shí)向主 NameNode 和備 NameNode 上報(bào)數(shù)據(jù)塊的位置信息。
NameNode 的主備切換實(shí)現(xiàn):
NameNode 主備切換主要由 ZKFailoverController、HealthMonitor 和 ActiveStandbyElector 這 3 個(gè)組件來(lái)協(xié)同實(shí)現(xiàn):
- ZKFailoverController 作為 NameNode 機(jī)器上一個(gè)獨(dú)立的進(jìn)程啟動(dòng) (在 hdfs 啟動(dòng)腳本之中的進(jìn)程名為 zkfc),啟動(dòng)的時(shí)候會(huì)創(chuàng)建 HealthMonitor 和 ActiveStandbyElector 這兩個(gè)主要的內(nèi)部組件,ZKFailoverController 在創(chuàng)建 HealthMonitor 和 ActiveStandbyElector 的同時(shí),也會(huì)向 HealthMonitor 和 ActiveStandbyElector 注冊(cè)相應(yīng)的回調(diào)方法。
- HealthMonitor 主要負(fù)責(zé)檢測(cè) NameNode 的健康狀態(tài),如果檢測(cè)到 NameNode 的狀態(tài)發(fā)生變化,會(huì)回調(diào) ZKFailoverController 的相應(yīng)方法進(jìn)行自動(dòng)的主備選舉。
- ActiveStandbyElector 主要負(fù)責(zé)完成自動(dòng)的主備選舉,內(nèi)部封裝了 Zookeeper 的處理邏輯,一旦 Zookeeper 主備選舉完成,會(huì)回調(diào) ZKFailoverController 的相應(yīng)方法來(lái)進(jìn)行 NameNode 的主備狀態(tài)切換。
NameNode 的主備切換流程
步驟如下:
- HealthMonitor 初始化完成之后會(huì)啟動(dòng)內(nèi)部的線程來(lái)定時(shí)調(diào)用對(duì)應(yīng) NameNode 的 HAServiceProtocol RPC 接口的方法,對(duì) NameNode 的健康狀態(tài)進(jìn)行檢測(cè)。
- HealthMonitor 如果檢測(cè)到 NameNode 的健康狀態(tài)發(fā)生變化,會(huì)回調(diào) ZKFailoverController 注冊(cè)的相應(yīng)方法進(jìn)行處理。
- 如果 ZKFailoverController 判斷需要進(jìn)行主備切換,會(huì)首先使用 ActiveStandbyElector 來(lái)進(jìn)行自動(dòng)的主備選舉。
- ActiveStandbyElector 與 Zookeeper 進(jìn)行交互完成自動(dòng)的主備選舉。
- ActiveStandbyElector 在主備選舉完成后,會(huì)回調(diào) ZKFailoverController 的相應(yīng)方法來(lái)通知當(dāng)前的 NameNode 成為主 NameNode 或備 NameNode。
- ZKFailoverController 調(diào)用對(duì)應(yīng) NameNode 的 HAServiceProtocol RPC 接口的方法將 NameNode 轉(zhuǎn)換為 Active 狀態(tài)或 Standby 狀態(tài)。
接著說(shuō)一下 HealthMonitor、ActiveStandbyElector 和 ZKFailoverController 的實(shí)現(xiàn)細(xì)節(jié)。
HealthMonitor 實(shí)現(xiàn)分析
ZKFailoverController 在初始化的時(shí)候會(huì)創(chuàng)建 HealthMonitor,HealthMonitor 在內(nèi)部會(huì)啟動(dòng)一個(gè)線程來(lái)循環(huán)調(diào)用 NameNode 的 HAServiceProtocol RPC 接口的方法來(lái)檢測(cè) NameNode 的狀態(tài),并將狀態(tài)的變化通過(guò)回調(diào)的方式來(lái)通知 ZKFailoverController。
HealthMonitor 主要檢測(cè) NameNode 的兩類狀態(tài),分別是 HealthMonitor.State 和 HAServiceStatus。HealthMonitor.State 是通過(guò) HAServiceProtocol RPC 接口的 monitorHealth 方法來(lái)獲取的,反映了 NameNode 節(jié)點(diǎn)的健康狀況,主要是磁盤存儲(chǔ)資源是否充足。HealthMonitor.State 包括下面幾種狀態(tài):
- INITIALIZING:HealthMonitor 在初始化過(guò)程中,還沒(méi)有開(kāi)始進(jìn)行健康狀況檢測(cè);
- SERVICE_HEALTHY:NameNode 狀態(tài)正常;
- SERVICE_NOT_RESPONDING:調(diào)用 NameNode 的 monitorHealth 方法調(diào)用無(wú)響應(yīng)或響應(yīng)超時(shí);
- SERVICE_UNHEALTHY:NameNode 還在運(yùn)行,但是 monitorHealth 方法返回狀態(tài)不正常,磁盤存儲(chǔ)資源不足;
- HEALTH_MONITOR_FAILED:HealthMonitor 自己在運(yùn)行過(guò)程中發(fā)生了異常,不能繼續(xù)檢測(cè) NameNode 的健康狀況,會(huì)導(dǎo)致 ZKFailoverController 進(jìn)程退出;
HealthMonitor.State 在狀態(tài)檢測(cè)之中起主要的作用,在 HealthMonitor.State 發(fā)生變化的時(shí)候,HealthMonitor 會(huì)回調(diào) ZKFailoverController 的相應(yīng)方法來(lái)進(jìn)行處理。
而 HAServiceStatus 則是通過(guò) HAServiceProtocol RPC 接口的 getServiceStatus 方法來(lái)獲取的,主要反映的是 NameNode 的 HA 狀態(tài),包括:
- INITIALIZING:NameNode 在初始化過(guò)程中;
- ACTIVE:當(dāng)前 NameNode 為主 NameNode;
- STANDBY:當(dāng)前 NameNode 為備 NameNode;
- STOPPING:當(dāng)前 NameNode 已停止;
HAServiceStatus 在狀態(tài)檢測(cè)之中只是起輔助的作用,在 HAServiceStatus 發(fā)生變化時(shí),HealthMonitor 也會(huì)回調(diào) ZKFailoverController 的相應(yīng)方法來(lái)進(jìn)行處理,具體處理見(jiàn)后文 ZKFailoverController 部分所述。
ActiveStandbyElector 實(shí)現(xiàn)分析
Namenode(包括 YARN ResourceManager) 的主備選舉是通過(guò) ActiveStandbyElector 來(lái)完成的,ActiveStandbyElector 主要是利用了 Zookeeper 的寫一致性和臨時(shí)節(jié)點(diǎn)機(jī)制,具體的主備選舉實(shí)現(xiàn)如下:
- 創(chuàng)建鎖節(jié)點(diǎn)
如果 HealthMonitor 檢測(cè)到對(duì)應(yīng)的 NameNode 的狀態(tài)正常,那么表示這個(gè) NameNode 有資格參加 Zookeeper 的主備選舉。
如果目前還沒(méi)有進(jìn)行過(guò)主備選舉的話,那么相應(yīng)的 ActiveStandbyElector 就會(huì)發(fā)起一次主備選舉,嘗試在 Zookeeper 上創(chuàng)建一個(gè)路徑為/hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock 的臨時(shí)節(jié)點(diǎn) ({dfs.nameservices} 為 Hadoop 的配置參數(shù) dfs.nameservices 的值,下同),Zookeeper 的寫一致性會(huì)保證最終只會(huì)有一個(gè) ActiveStandbyElector 創(chuàng)建成功,那么創(chuàng)建成功的 ActiveStandbyElector 對(duì)應(yīng)的 NameNode 就會(huì)成為主 NameNode,ActiveStandbyElector 會(huì)回調(diào) ZKFailoverController 的方法進(jìn)一步將對(duì)應(yīng)的 NameNode 切換為 Active 狀態(tài)。
而創(chuàng)建失敗的 ActiveStandbyElector 對(duì)應(yīng)的 NameNode 成為備 NameNode,ActiveStandbyElector 會(huì)回調(diào) ZKFailoverController 的方法進(jìn)一步將對(duì)應(yīng)的 NameNode 切換為 Standby 狀態(tài)。
- 注冊(cè) Watcher 監(jiān)聽(tīng)
不管創(chuàng)建/hadoop-ha/${dfs.nameservices}/ActiveStandbyElectorLock 節(jié)點(diǎn)是否成功,ActiveStandbyElector 隨后都會(huì)向 Zookeeper 注冊(cè)一個(gè) Watcher 來(lái)監(jiān)聽(tīng)這個(gè)節(jié)點(diǎn)的狀態(tài)變化事件,ActiveStandbyElector 主要關(guān)注這個(gè)節(jié)點(diǎn)的 NodeDeleted 事件。
- 自動(dòng)觸發(fā)主備選舉
如果 Active NameNode 對(duì)應(yīng)的 HealthMonitor 檢測(cè)到 NameNode 的狀態(tài)異常時(shí), ZKFailoverController 會(huì)主動(dòng)刪除當(dāng)前在 Zookeeper 上建立的臨時(shí)節(jié)點(diǎn)/hadoop-ha/${dfs.nameservices}/ActiveStandbyElectorLock,這樣處于 Standby 狀態(tài)的 NameNode 的 ActiveStandbyElector 注冊(cè)的監(jiān)聽(tīng)器就會(huì)收到這個(gè)節(jié)點(diǎn)的 NodeDeleted 事件。
收到這個(gè)事件之后,會(huì)馬上再次進(jìn)入到創(chuàng)建/hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock 節(jié)點(diǎn)的流程,如果創(chuàng)建成功,這個(gè)本來(lái)處于 Standby 狀態(tài)的 NameNode 就選舉為主 NameNode 并隨后開(kāi)始切換為 Active 狀態(tài)。 當(dāng)然,如果是 Active 狀態(tài)的 NameNode 所在的機(jī)器整個(gè)宕掉的話,那么根據(jù) Zookeeper 的臨時(shí)節(jié)點(diǎn)特性,/hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock 節(jié)點(diǎn)會(huì)自動(dòng)被刪除,從而也會(huì)自動(dòng)進(jìn)行一次主備切換。
- 防止腦裂
Zookeeper 在工程實(shí)踐的過(guò)程中經(jīng)常會(huì)發(fā)生的一個(gè)現(xiàn)象就是 Zookeeper 客戶端“假死”,所謂的“假死”是指如果 Zookeeper 客戶端機(jī)器負(fù)載過(guò)高或者正在進(jìn)行 JVM Full GC,那么可能會(huì)導(dǎo)致 Zookeeper 客戶端到 Zookeeper 服務(wù)端的心跳不能正常發(fā)出,一旦這個(gè)時(shí)間持續(xù)較長(zhǎng),超過(guò)了配置的 Zookeeper Session Timeout 參數(shù)的話,Zookeeper 服務(wù)端就會(huì)認(rèn)為客戶端的 session 已經(jīng)過(guò)期從而將客戶端的 Session 關(guān)閉。
“假死”有可能引起分布式系統(tǒng)常說(shuō)的雙主或腦裂 (brain-split) 現(xiàn)象。
具體到本文所述的 NameNode,假設(shè) NameNode1 當(dāng)前為 Active 狀態(tài),NameNode2 當(dāng)前為 Standby 狀態(tài)。
如果某一時(shí)刻 NameNode1 對(duì)應(yīng)的 ZKFailoverController 進(jìn)程發(fā)生了“假死”現(xiàn)象,那么 Zookeeper 服務(wù)端會(huì)認(rèn)為 NameNode1 掛掉了,根據(jù)前面的主備切換邏輯,NameNode2 會(huì)替代 NameNode1 進(jìn)入 Active 狀態(tài)。
但是此時(shí) NameNode1 可能仍然處于 Active 狀態(tài)正常運(yùn)行,即使隨后 NameNode1 對(duì)應(yīng)的 ZKFailoverController 因?yàn)樨?fù)載下降或者 Full GC 結(jié)束而恢復(fù)了正常,感知到自己和 Zookeeper 的 Session 已經(jīng)關(guān)閉,但是由于網(wǎng)絡(luò)的延遲以及 CPU 線程調(diào)度的不確定性,仍然有可能會(huì)在接下來(lái)的一段時(shí)間窗口內(nèi) NameNode1 認(rèn)為自己還是處于 Active 狀態(tài)。這樣 NameNode1 和 NameNode2 都處于 Active 狀態(tài),都可以對(duì)外提供服務(wù)。
這種情況對(duì)于 NameNode 這類對(duì)數(shù)據(jù)一致性要求非常高的系統(tǒng)來(lái)說(shuō)是災(zāi)難性的,數(shù)據(jù)會(huì)發(fā)生錯(cuò)亂且無(wú)法恢復(fù)。Zookeeper 社區(qū)對(duì)這種問(wèn)題的解決方法叫做 fencing,中文翻譯為隔離,也就是想辦法把舊的 Active NameNode 隔離起來(lái),使它不能正常對(duì)外提供服務(wù)。
ActiveStandbyElector 為了實(shí)現(xiàn) fencing,會(huì)在成功創(chuàng)建 Zookeeper 節(jié)點(diǎn) hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock 從而成為 Active NameNode 之后,創(chuàng)建另外一個(gè)路徑為/hadoop-ha/{dfs.nameservices}/ActiveBreadCrumb 的持久節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)里面保存了這個(gè) Active NameNode 的地址信息。
Active NameNode 的 ActiveStandbyElector 在正常的狀態(tài)下關(guān)閉 Zookeeper Session 的時(shí)候 (注意由于/hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock 是臨時(shí)節(jié)點(diǎn),也會(huì)隨之刪除),會(huì)一起刪除節(jié)點(diǎn)/hadoop-ha/{dfs.nameservices}/ActiveBreadCrumb。
但是如果 ActiveStandbyElector 在異常的狀態(tài)下 Zookeeper Session 關(guān)閉 (比如前述的 Zookeeper 假死),那么由于/hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb 是持久節(jié)點(diǎn),會(huì)一直保留下來(lái)。
后面當(dāng)另一個(gè) NameNode 選主成功之后,會(huì)注意到上一個(gè) Active NameNode 遺留下來(lái)的這個(gè)節(jié)點(diǎn),從而會(huì)回調(diào) ZKFailoverController 的方法對(duì)舊的 Active NameNode 進(jìn)行 fencing,具體處理見(jiàn)后文 ZKFailoverController 部分所述。
ZKFailoverController 實(shí)現(xiàn)分析
ZKFailoverController 在創(chuàng)建 HealthMonitor 和 ActiveStandbyElector 的同時(shí),會(huì)向 HealthMonitor 和 ActiveStandbyElector 注冊(cè)相應(yīng)的回調(diào)函數(shù),ZKFailoverController 的處理邏輯主要靠 HealthMonitor 和 ActiveStandbyElector 的回調(diào)函數(shù)來(lái)驅(qū)動(dòng)。
- 對(duì) HealthMonitor 狀態(tài)變化的處理
如前所述,HealthMonitor 會(huì)檢測(cè) NameNode 的兩類狀態(tài),HealthMonitor.State 在狀態(tài)檢測(cè)之中起主要的作用,ZKFailoverController 注冊(cè)到 HealthMonitor 上的處理 HealthMonitor.State 狀態(tài)變化的回調(diào)函數(shù)主要關(guān)注 SERVICE_HEALTHY、SERVICE_NOT_RESPONDING 和 SERVICE_UNHEALTHY 這 3 種狀態(tài):
- 如果檢測(cè)到狀態(tài)為 SERVICE_HEALTHY,表示當(dāng)前的 NameNode 有資格參加 Zookeeper 的主備選舉,如果目前還沒(méi)有進(jìn)行過(guò)主備選舉的話,ZKFailoverController 會(huì)調(diào)用 ActiveStandbyElector 的 joinElection 方法發(fā)起一次主備選舉。
- 如果檢測(cè)到狀態(tài)為 SERVICE_NOT_RESPONDING 或者是 SERVICE_UNHEALTHY,就表示當(dāng)前的 NameNode 出現(xiàn)問(wèn)題了,ZKFailoverController 會(huì)調(diào)用 ActiveStandbyElector 的 quitElection 方法刪除當(dāng)前已經(jīng)在 Zookeeper 上建立的臨時(shí)節(jié)點(diǎn)退出主備選舉,這樣其它的 NameNode 就有機(jī)會(huì)成為主 NameNode。
而 HAServiceStatus 在狀態(tài)檢測(cè)之中僅起輔助的作用,在 HAServiceStatus 發(fā)生變化時(shí),ZKFailoverController 注冊(cè)到 HealthMonitor 上的處理 HAServiceStatus 狀態(tài)變化的回調(diào)函數(shù)會(huì)判斷 NameNode 返回的 HAServiceStatus 和 ZKFailoverController 所期望的是否一致,如果不一致的話,ZKFailoverController 也會(huì)調(diào)用 ActiveStandbyElector 的 quitElection 方法刪除當(dāng)前已經(jīng)在 Zookeeper 上建立的臨時(shí)節(jié)點(diǎn)退出主備選舉。
- 對(duì) ActiveStandbyElector 主備選舉狀態(tài)變化的處理
在 ActiveStandbyElector 的主備選舉狀態(tài)發(fā)生變化時(shí),會(huì)回調(diào) ZKFailoverController 注冊(cè)的回調(diào)函數(shù)來(lái)進(jìn)行相應(yīng)的處理:
- 如果 ActiveStandbyElector 選主成功,那么 ActiveStandbyElector 對(duì)應(yīng)的 NameNode 成為主 NameNode,ActiveStandbyElector 會(huì)回調(diào) ZKFailoverController 的 becomeActive 方法,這個(gè)方法通過(guò)調(diào)用對(duì)應(yīng)的 NameNode 的 HAServiceProtocol RPC 接口的 transitionToActive 方法,將 NameNode 轉(zhuǎn)換為 Active 狀態(tài)。
- 如果 ActiveStandbyElector 選主失敗,那么 ActiveStandbyElector 對(duì)應(yīng)的 NameNode 成為備 NameNode,ActiveStandbyElector 會(huì)回調(diào) ZKFailoverController 的 becomeStandby 方法,這個(gè)方法通過(guò)調(diào)用對(duì)應(yīng)的 NameNode 的 HAServiceProtocol RPC 接口的 transitionToStandby 方法,將 NameNode 轉(zhuǎn)換為 Standby 狀態(tài)。
- 如果 ActiveStandbyElector 選主成功之后,發(fā)現(xiàn)了上一個(gè) Active NameNode 遺留下來(lái)的/hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb 節(jié)點(diǎn) (見(jiàn)“ActiveStandbyElector 實(shí)現(xiàn)分析”一節(jié)“防止腦裂”部分所述),那么 ActiveStandbyElector 會(huì)首先回調(diào) ZKFailoverController 注冊(cè)的 fenceOldActive 方法,嘗試對(duì)舊的 Active NameNode 進(jìn)行 fencing,在進(jìn)行 fencing 的時(shí)候,會(huì)執(zhí)行以下的操作:
- 首先嘗試調(diào)用這個(gè)舊 Active NameNode 的 HAServiceProtocol RPC 接口的 transitionToStandby 方法,看能不能把它轉(zhuǎn)換為 Standby 狀態(tài)。
- 如果 transitionToStandby 方法調(diào)用失敗,那么就執(zhí)行 Hadoop 配置文件之中預(yù)定義的隔離措施,Hadoop 目前主要提供兩種隔離措施,通常會(huì)選擇 sshfence:
- sshfence:通過(guò) SSH 登錄到目標(biāo)機(jī)器上,執(zhí)行命令 fuser 將對(duì)應(yīng)的進(jìn)程殺死;
- shellfence:執(zhí)行一個(gè)用戶自定義的 shell 腳本來(lái)將對(duì)應(yīng)的進(jìn)程隔離;
只有在成功地執(zhí)行完成 fencing 之后,選主成功的 ActiveStandbyElector 才會(huì)回調(diào) ZKFailoverController 的 becomeActive 方法將對(duì)應(yīng)的 NameNode 轉(zhuǎn)換為 Active 狀態(tài),開(kāi)始對(duì)外提供服務(wù)。
NameNode 的共享存儲(chǔ)實(shí)現(xiàn)
NameNode 的元數(shù)據(jù)存儲(chǔ)概述
一個(gè)典型的 NameNode 的元數(shù)據(jù)存儲(chǔ)目錄結(jié)構(gòu)如圖 3 所示這里主要關(guān)注其中的 EditLog 文件和 FSImage 文件:
NameNode 的元數(shù)據(jù)存儲(chǔ)目錄結(jié)構(gòu)
NameNode 在執(zhí)行 HDFS 客戶端提交的創(chuàng)建文件或者移動(dòng)文件這樣的寫操作的時(shí)候,會(huì)首先把這些操作記錄在 EditLog 文件之中,然后再更新內(nèi)存中的文件系統(tǒng)鏡像。
內(nèi)存中的文件系統(tǒng)鏡像用于 NameNode 向客戶端提供讀服務(wù),而 EditLog 僅僅只是在數(shù)據(jù)恢復(fù)的時(shí)候起作用。
記錄在 EditLog 之中的每一個(gè)操作又稱為一個(gè)事務(wù),每個(gè)事務(wù)有一個(gè)整數(shù)形式的事務(wù) id 作為編號(hào)。
EditLog 會(huì)被切割為很多段,每一段稱為一個(gè) Segment。
正在寫入的 EditLog Segment 處于 in-progress 狀態(tài),其文件名形如 edits_inprogress_{start_txid},其中{start_txid} 表示這個(gè) segment 的起始事務(wù) id,例如上圖中的 edits_inprogress_0000000000000000020。
而已經(jīng)寫入完成的 EditLog Segment 處于 finalized 狀態(tài),其文件名形如 edits_{start_txid}-{end_txid},其中{start_txid} 表示這個(gè) segment 的起始事務(wù) id,{end_txid} 表示這個(gè) segment 的結(jié)束事務(wù) id,例如上圖中的 edits_0000000000000000001-0000000000000000019。NameNode 會(huì)定期對(duì)內(nèi)存中的文件系統(tǒng)鏡像進(jìn)行 checkpoint 操作,在磁盤上生成 FSImage 文件,F(xiàn)SImage 文件的文件名形如 fsimage_{end_txid},其中{end_txid} 表示這個(gè) fsimage 文件的結(jié)束事務(wù) id,例如上圖中的 fsimage_0000000000000000020。
在 NameNode 啟動(dòng)的時(shí)候會(huì)進(jìn)行數(shù)據(jù)恢復(fù),首先把 FSImage 文件加載到內(nèi)存中形成文件系統(tǒng)鏡像,然后再把 EditLog 之中 FsImage 的結(jié)束事務(wù) id 之后的 EditLog 回放到這個(gè)文件系統(tǒng)鏡像上。
基于 QJM 的共享存儲(chǔ)系統(tǒng)的總體架構(gòu)
基于 QJM 的共享存儲(chǔ)系統(tǒng)主要用于保存 EditLog,并不保存 FSImage 文件。
FSImage 文件還是在 NameNode 的本地磁盤上。
QJM 共享存儲(chǔ)的基本思想來(lái)自于 Paxos 算法,采用多個(gè)稱為 JournalNode 的節(jié)點(diǎn)組成的 JournalNode 集群來(lái)存儲(chǔ) EditLog。
每個(gè) JournalNode 保存同樣的 EditLog 副本。
每次 NameNode 寫 EditLog 的時(shí)候,除了向本地磁盤寫入 EditLog 之外,也會(huì)并行地向 JournalNode 集群之中的每一個(gè) JournalNode 發(fā)送寫請(qǐng)求,只要大多數(shù) (majority) 的 JournalNode 節(jié)點(diǎn)返回成功就認(rèn)為向 JournalNode 集群寫入 EditLog 成功。
如果有 2N+1 臺(tái) JournalNode,那么根據(jù)大多數(shù)的原則,最多可以容忍有 N 臺(tái) JournalNode 節(jié)點(diǎn)掛掉。
基于 QJM 的共享存儲(chǔ)系統(tǒng)的內(nèi)部實(shí)現(xiàn)架構(gòu)圖如圖 4 所示,主要包含下面幾個(gè)主要的組件:
基于 QJM 的共享存儲(chǔ)系統(tǒng)的內(nèi)部實(shí)現(xiàn)架構(gòu)圖
- FSEditLog:這個(gè)類封裝了對(duì) EditLog 的所有操作,是 NameNode 對(duì) EditLog 的所有操作的入口。
- JournalSet: 這個(gè)類封裝了對(duì)本地磁盤和 JournalNode 集群上的 EditLog 的操作,內(nèi)部包含了兩類 JournalManager,一類為 FileJournalManager,用于實(shí)現(xiàn)對(duì)本地磁盤上 EditLog 的操作。一類為 QuorumJournalManager,用于實(shí)現(xiàn)對(duì) JournalNode 集群上共享目錄的 EditLog 的操作。FSEditLog 只會(huì)調(diào)用 JournalSet 的相關(guān)方法,而不會(huì)直接使用 FileJournalManager 和 QuorumJournalManager。
- FileJournalManager:封裝了對(duì)本地磁盤上的 EditLog 文件的操作,不僅 NameNode 在向本地磁盤上寫入 EditLog 的時(shí)候使用 FileJournalManager,JournalNode 在向本地磁盤寫入 EditLog 的時(shí)候也復(fù)用了 FileJournalManager 的代碼和邏輯。
- QuorumJournalManager:封裝了對(duì) JournalNode 集群上的 EditLog 的操作,它會(huì)根據(jù) JournalNode 集群的 URI 創(chuàng)建負(fù)責(zé)與 JournalNode 集群通信的類 AsyncLoggerSet, QuorumJournalManager 通過(guò) AsyncLoggerSet 來(lái)實(shí)現(xiàn)對(duì) JournalNode 集群上的 EditLog 的寫操作,對(duì)于讀操作,QuorumJournalManager 則是通過(guò) Http 接口從 JournalNode 上的 JournalNodeHttpServer 讀取 EditLog 的數(shù)據(jù)。
- AsyncLoggerSet:內(nèi)部包含了與 JournalNode 集群進(jìn)行通信的 AsyncLogger 列表,每一個(gè) AsyncLogger 對(duì)應(yīng)于一個(gè) JournalNode 節(jié)點(diǎn),另外 AsyncLoggerSet 也包含了用于等待大多數(shù) JournalNode 返回結(jié)果的工具類方法給 QuorumJournalManager 使用。
- AsyncLogger:具體的實(shí)現(xiàn)類是 IPCLoggerChannel,IPCLoggerChannel 在執(zhí)行方法調(diào)用的時(shí)候,會(huì)把調(diào)用提交到一個(gè)單線程的線程池之中,由線程池線程來(lái)負(fù)責(zé)向?qū)?yīng)的 JournalNode 的 JournalNodeRpcServer 發(fā)送 RPC 請(qǐng)求。
- JournalNodeRpcServer:運(yùn)行在 JournalNode 節(jié)點(diǎn)進(jìn)程中的 RPC 服務(wù),接收 NameNode 端的 AsyncLogger 的 RPC 請(qǐng)求。
- JournalNodeHttpServer:運(yùn)行在 JournalNode 節(jié)點(diǎn)進(jìn)程中的 Http 服務(wù),用于接收處于 Standby 狀態(tài)的 NameNode 和其它 JournalNode 的同步 EditLog 文件流的請(qǐng)求。
基于 QJM 的共享存儲(chǔ)系統(tǒng)的數(shù)據(jù)同步機(jī)制分析
Active NameNode 和 StandbyNameNode 使用 JouranlNode 集群來(lái)進(jìn)行數(shù)據(jù)同步的過(guò)程如圖 5 所示,Active NameNode 首先把 EditLog 提交到 JournalNode 集群,然后 Standby NameNode 再?gòu)?JournalNode 集群定時(shí)同步 EditLog:
基于 QJM 的共享存儲(chǔ)的數(shù)據(jù)同步機(jī)制
- Active NameNode 提交 EditLog 到 JournalNode 集群
當(dāng)處于 Active 狀態(tài)的 NameNode 調(diào)用 FSEditLog 類的 logSync 方法來(lái)提交 EditLog 的時(shí)候,會(huì)通過(guò) JouranlSet 同時(shí)向本地磁盤目錄和 JournalNode 集群上的共享存儲(chǔ)目錄寫入 EditLog。
寫入 JournalNode 集群是通過(guò)并行調(diào)用每一個(gè) JournalNode 的 QJournalProtocol RPC 接口的 journal 方法實(shí)現(xiàn)的,如果對(duì)大多數(shù) JournalNode 的 journal 方法調(diào)用成功,那么就認(rèn)為提交 EditLog 成功,否則 NameNode 就會(huì)認(rèn)為這次提交 EditLog 失敗。
提交 EditLog 失敗會(huì)導(dǎo)致 Active NameNode 關(guān)閉 JournalSet 之后退出進(jìn)程,留待處于 Standby 狀態(tài)的 NameNode 接管之后進(jìn)行數(shù)據(jù)恢復(fù)。
從上面的敘述可以看出,Active NameNode 提交 EditLog 到 JournalNode 集群的過(guò)程實(shí)際上是同步阻塞的,但是并不需要所有的 JournalNode 都調(diào)用成功,只要大多數(shù) JournalNode 調(diào)用成功就可以了。
如果無(wú)法形成大多數(shù),那么就認(rèn)為提交 EditLog 失敗,NameNode 停止服務(wù)退出進(jìn)程。
如果對(duì)應(yīng)到分布式系統(tǒng)的 CAP 理論的話,雖然采用了 Paxos 的“大多數(shù)”思想對(duì) C(consistency,一致性) 和 A(availability,可用性) 進(jìn)行了折衷,但還是可以認(rèn)為 NameNode 選擇了 C 而放棄了 A,這也符合 NameNode 對(duì)數(shù)據(jù)一致性的要求。
- Standby NameNode 從 JournalNode 集群同步 EditLog
當(dāng) NameNode 進(jìn)入 Standby 狀態(tài)之后,會(huì)啟動(dòng)一個(gè) EditLogTailer 線程。
這個(gè)線程會(huì)定期調(diào)用 EditLogTailer 類的 doTailEdits 方法從 JournalNode 集群上同步 EditLog,然后把同步的 EditLog 回放到內(nèi)存之中的文件系統(tǒng)鏡像上 (并不會(huì)同時(shí)把 EditLog 寫入到本地磁盤上)。
這里需要關(guān)注的是:從 JournalNode 集群上同步的 EditLog 都是處于 finalized 狀態(tài)的 EditLog Segment。
“NameNode 的元數(shù)據(jù)存儲(chǔ)概述”一節(jié)說(shuō)過(guò) EditLog Segment 實(shí)際上有兩種狀態(tài),處于 in-progress 狀態(tài)的 Edit Log 當(dāng)前正在被寫入,被認(rèn)為是處于不穩(wěn)定的中間態(tài),有可能會(huì)在后續(xù)的過(guò)程之中發(fā)生修改,比如被截?cái)唷?/p>
Active NameNode 在完成一個(gè) EditLog Segment 的寫入之后,就會(huì)向 JournalNode 集群發(fā)送 finalizeLogSegment RPC 請(qǐng)求,將完成寫入的 EditLog Segment finalized,然后開(kāi)始下一個(gè)新的 EditLog Segment。
一旦 finalizeLogSegment 方法在大多數(shù)的 JournalNode 上調(diào)用成功,表明這個(gè) EditLog Segment 已經(jīng)在大多數(shù)的 JournalNode 上達(dá)成一致。
一個(gè) EditLog Segment 處于 finalized 狀態(tài)之后,可以保證它再也不會(huì)變化。
從上面描述的過(guò)程可以看出,雖然 Active NameNode 向 JournalNode 集群提交 EditLog 是同步的,但 Standby NameNode 采用的是定時(shí)從 JournalNode 集群上同步 EditLog 的方式,那么 Standby NameNode 內(nèi)存中文件系統(tǒng)鏡像有很大的可能是落后于 Active NameNode 的,所以 Standby NameNode 在轉(zhuǎn)換為 Active NameNode 的時(shí)候需要把落后的 EditLog 補(bǔ)上來(lái)。
基于 QJM 的共享存儲(chǔ)系統(tǒng)的數(shù)據(jù)恢復(fù)機(jī)制分析
處于 Standby 狀態(tài)的 NameNode 轉(zhuǎn)換為 Active 狀態(tài)的時(shí)候,有可能上一個(gè) Active NameNode 發(fā)生了異常退出,那么 JournalNode 集群中各個(gè) JournalNode 上的 EditLog 就可能會(huì)處于不一致的狀態(tài),所以首先要做的事情就是讓 JournalNode 集群中各個(gè)節(jié)點(diǎn)上的 EditLog 恢復(fù)為一致。
另外如前所述,當(dāng)前處于 Standby 狀態(tài)的 NameNode 的內(nèi)存中的文件系統(tǒng)鏡像有很大的可能是落后于舊的 Active NameNode 的,所以在 JournalNode 集群中各個(gè)節(jié)點(diǎn)上的 EditLog 達(dá)成一致之后,接下來(lái)要做的事情就是從 JournalNode 集群上補(bǔ)齊落后的 EditLog。
只有在這兩步完成之后,當(dāng)前新的 Active NameNode 才能安全地對(duì)外提供服務(wù)。
補(bǔ)齊落后的 EditLog 的過(guò)程復(fù)用了前面描述的 Standby NameNode 從 JournalNode 集群同步 EditLog 的邏輯和代碼,最終調(diào)用 EditLogTailer 類的 doTailEdits 方法來(lái)完成 EditLog 的補(bǔ)齊。
使 JournalNode 集群上的 EditLog 達(dá)成一致的過(guò)程是一致性算法 Paxos 的典型應(yīng)用場(chǎng)景,QJM 對(duì)這部分的處理可以看做是 Single Instance Paxos 算法的一個(gè)實(shí)現(xiàn),在達(dá)成一致的過(guò)程中,Active NameNode 和 JournalNode 集群之間的交互流程如圖 6 所示,具體描述如下:
Active NameNode 和 JournalNode 集群的交互流程圖
- 生成一個(gè)新的 Epoch
Epoch 是一個(gè)單調(diào)遞增的整數(shù),用來(lái)標(biāo)識(shí)每一次 Active NameNode 的生命周期,每發(fā)生一次 NameNode 的主備切換,Epoch 就會(huì)加 1。
這實(shí)際上是一種 fencing 機(jī)制,為什么需要 fencing 已經(jīng)在前面“ActiveStandbyElector 實(shí)現(xiàn)分析”一節(jié)的“防止腦裂”部分進(jìn)行了說(shuō)明。
產(chǎn)生新 Epoch 的流程與 Zookeeper 的 ZAB(Zookeeper Atomic Broadcast) 協(xié)議在進(jìn)行數(shù)據(jù)恢復(fù)之前產(chǎn)生新 Epoch 的過(guò)程完全類似:
- Active NameNode 首先向 JournalNode 集群發(fā)送 getJournalState RPC 請(qǐng)求,每個(gè) JournalNode 會(huì)返回自己保存的最近的那個(gè) Epoch(代碼中叫 lastPromisedEpoch)。
- NameNode 收到大多數(shù)的 JournalNode 返回的 Epoch 之后,在其中選擇最大的一個(gè)加 1 作為當(dāng)前的新 Epoch,然后向各個(gè) JournalNode 發(fā)送 newEpoch RPC 請(qǐng)求,把這個(gè)新的 Epoch 發(fā)給各個(gè) JournalNode。
- 每一個(gè) JournalNode 在收到新的 Epoch 之后,首先檢查這個(gè)新的 Epoch 是否比它本地保存的 lastPromisedEpoch 大,如果大的話就把 lastPromisedEpoch 更新為這個(gè)新的 Epoch,并且向 NameNode 返回它自己的本地磁盤上最新的一個(gè) EditLogSegment 的起始事務(wù) id,為后面的數(shù)據(jù)恢復(fù)過(guò)程做好準(zhǔn)備。如果小于或等于的話就向 NameNode 返回錯(cuò)誤。
- NameNode 收到大多數(shù) JournalNode 對(duì) newEpoch 的成功響應(yīng)之后,就會(huì)認(rèn)為生成新的 Epoch 成功。
在生成新的 Epoch 之后,每次 NameNode 在向 JournalNode 集群提交 EditLog 的時(shí)候,都會(huì)把這個(gè) Epoch 作為參數(shù)傳遞過(guò)去。
每個(gè) JournalNode 會(huì)比較傳過(guò)來(lái)的 Epoch 和它自己保存的 lastPromisedEpoch 的大小,如果傳過(guò)來(lái)的 epoch 的值比它自己保存的 lastPromisedEpoch 小的話,那么這次寫相關(guān)操作會(huì)被拒絕。
一旦大多數(shù) JournalNode 都拒絕了這次寫操作,那么這次寫操作就失敗了。
如果原來(lái)的 Active NameNode 恢復(fù)正常之后再向 JournalNode 寫 EditLog,那么因?yàn)樗?Epoch 肯定比新生成的 Epoch 小,并且大多數(shù)的 JournalNode 都接受了這個(gè)新生成的 Epoch,所以拒絕寫入的 JournalNode 數(shù)目至少是大多數(shù),這樣原來(lái)的 Active NameNode 寫 EditLog 就肯定會(huì)失敗,失敗之后這個(gè) NameNode 進(jìn)程會(huì)直接退出,這樣就實(shí)現(xiàn)了對(duì)原來(lái)的 Active NameNode 的隔離了。
- 選擇需要數(shù)據(jù)恢復(fù)的 EditLog Segment 的 id
需要恢復(fù)的 Edit Log 只可能是各個(gè) JournalNode 上的最后一個(gè) Edit Log Segment,如前所述,JournalNode 在處理完 newEpoch RPC 請(qǐng)求之后,會(huì)向 NameNode 返回它自己的本地磁盤上最新的一個(gè) EditLog Segment 的起始事務(wù) id,這個(gè)起始事務(wù) id 實(shí)際上也作為這個(gè) EditLog Segment 的 id。
NameNode 會(huì)在所有這些 id 之中選擇一個(gè)最大的 id 作為要進(jìn)行數(shù)據(jù)恢復(fù)的 EditLog Segment 的 id。
- 向 JournalNode 集群發(fā)送 prepareRecovery RPC 請(qǐng)求
NameNode 接下來(lái)向 JournalNode 集群發(fā)送 prepareRecovery RPC 請(qǐng)求,請(qǐng)求的參數(shù)就是選出的 EditLog Segment 的 id。
JournalNode 收到請(qǐng)求后返回本地磁盤上這個(gè) Segment 的起始事務(wù) id、結(jié)束事務(wù) id 和狀態(tài) (in-progress 或 finalized)。
這一步對(duì)應(yīng)于 Paxos 算法的 Phase 1a 和 Phase 1b(參見(jiàn)參考文獻(xiàn) [3]) 兩步。
Paxos 算法的 Phase1 是 prepare 階段,這也與方法名 prepareRecovery 相對(duì)應(yīng)。
并且這里以前面產(chǎn)生的新的 Epoch 作為 Paxos 算法中的提案編號(hào) (proposal number)。只要大多數(shù)的 JournalNode 的 prepareRecovery RPC 調(diào)用成功返回,NameNode 就認(rèn)為成功。
選擇進(jìn)行同步的基準(zhǔn)數(shù)據(jù)源,向 JournalNode 集群發(fā)送 acceptRecovery RPC 請(qǐng)求 NameNode 根據(jù) prepareRecovery 的返回結(jié)果,選擇一個(gè) JournalNode 上的 EditLog Segment 作為同步的基準(zhǔn)數(shù)據(jù)源。
選擇基準(zhǔn)數(shù)據(jù)源的原則大致是:在 in-progress 狀態(tài)和 finalized 狀態(tài)的 Segment 之間優(yōu)先選擇 finalized 狀態(tài)的 Segment。
如果都是 in-progress 狀態(tài)的話,那么優(yōu)先選擇 Epoch 比較高的 Segment(也就是優(yōu)先選擇更新的),如果 Epoch 也一樣,那么優(yōu)先選擇包含的事務(wù)數(shù)更多的 Segment。
在選定了同步的基準(zhǔn)數(shù)據(jù)源之后,NameNode 向 JournalNode 集群發(fā)送 acceptRecovery RPC 請(qǐng)求,將選定的基準(zhǔn)數(shù)據(jù)源作為參數(shù)。
JournalNode 接收到 acceptRecovery RPC 請(qǐng)求之后,從基準(zhǔn)數(shù)據(jù)源 JournalNode 的 JournalNodeHttpServer 上下載 EditLog Segment,將本地的 EditLog Segment 替換為下載的 EditLog Segment。
這一步對(duì)應(yīng)于 Paxos 算法的 Phase 2a 和 Phase 2b兩步。
Paxos 算法的 Phase2 是 accept 階段,這也與方法名 acceptRecovery 相對(duì)應(yīng)。
只要大多數(shù) JournalNode 的 acceptRecovery RPC 調(diào)用成功返回,NameNode 就認(rèn)為成功。
- 向 JournalNode 集群發(fā)送 finalizeLogSegment RPC 請(qǐng)求,數(shù)據(jù)恢復(fù)完成
上一步執(zhí)行完成之后,NameNode 確認(rèn)大多數(shù) JournalNode 上的 EditLog Segment 已經(jīng)從基準(zhǔn)數(shù)據(jù)源進(jìn)行了同步。
接下來(lái),NameNode 向 JournalNode 集群發(fā)送 finalizeLogSegment RPC 請(qǐng)求,JournalNode 接收到請(qǐng)求之后,將對(duì)應(yīng)的 EditLog Segment 從 in-progress 狀態(tài)轉(zhuǎn)換為 finalized 狀態(tài),實(shí)際上就是將文件名從 edits_inprogress_{startTxid} 重命名為 edits_{startTxid}-${endTxid},見(jiàn)“NameNode 的元數(shù)據(jù)存儲(chǔ)概述”一節(jié)的描述。
只要大多數(shù) JournalNode 的 finalizeLogSegment RPC 調(diào)用成功返回,NameNode 就認(rèn)為成功。
此時(shí)可以保證 JournalNode 集群的大多數(shù)節(jié)點(diǎn)上的 EditLog 已經(jīng)處于一致的狀態(tài),這樣 NameNode 才能安全地從 JournalNode 集群上補(bǔ)齊落后的 EditLog 數(shù)據(jù)。
NameNode 在進(jìn)行狀態(tài)轉(zhuǎn)換時(shí)對(duì)共享存儲(chǔ)的處理
- NameNode 初始化啟動(dòng),進(jìn)入 Standby 狀態(tài)
在 NameNode 以 HA 模式啟動(dòng)的時(shí)候,NameNode 會(huì)認(rèn)為自己處于 Standby 模式,在 NameNode 的構(gòu)造函數(shù)中會(huì)加載 FSImage 文件和 EditLog Segment 文件來(lái)恢復(fù)自己的內(nèi)存文件系統(tǒng)鏡像。
在加載 EditLog Segment 的時(shí)候,調(diào)用 FSEditLog 類的 initSharedJournalsForRead 方法來(lái)創(chuàng)建只包含了在 JournalNode 集群上的共享目錄的 JournalSet,也就是說(shuō),這個(gè)時(shí)候只會(huì)從 JournalNode 集群之中加載 EditLog,而不會(huì)加載本地磁盤上的 EditLog。
另外值得注意的是,加載的 EditLog Segment 只是處于 finalized 狀態(tài)的 EditLog Segment,而處于 in-progress 狀態(tài)的 Segment 需要后續(xù)在切換為 Active 狀態(tài)的時(shí)候,進(jìn)行一次數(shù)據(jù)恢復(fù)過(guò)程,將 in-progress 狀態(tài)的 Segment 轉(zhuǎn)換為 finalized 狀態(tài)的 Segment 之后再進(jìn)行讀取。
加載完 FSImage 文件和共享目錄上的 EditLog Segment 文件之后,NameNode 會(huì)啟動(dòng) EditLogTailer 線程和 StandbyCheckpointer 線程,正式進(jìn)入 Standby 模式。
如前所述,EditLogTailer 線程的作用是定時(shí)從 JournalNode 集群上同步 EditLog。
而 StandbyCheckpointer 線程的作用其實(shí)是為了替代 Hadoop 1.x 版本之中的 Secondary NameNode 的功能,StandbyCheckpointer 線程會(huì)在 Standby NameNode 節(jié)點(diǎn)上定期進(jìn)行 Checkpoint,將 Checkpoint 之后的 FSImage 文件上傳到 Active NameNode 節(jié)點(diǎn)。
- NameNode 從 Standby 狀態(tài)切換為 Active 狀態(tài)
當(dāng) NameNode 從 Standby 狀態(tài)切換為 Active 狀態(tài)的時(shí)候,首先需要做的就是停止它在 Standby 狀態(tài)的時(shí)候啟動(dòng)的線程和相關(guān)的服務(wù),包括上面提到的 EditLogTailer 線程和 StandbyCheckpointer 線程,然后關(guān)閉用于讀取 JournalNode 集群的共享目錄上的 EditLog 的 JournalSet,接下來(lái)會(huì)調(diào)用 FSEditLog 的 initJournalSetForWrite 方法重新打開(kāi) JournalSet。
不同的是,這個(gè) JournalSet 內(nèi)部同時(shí)包含了本地磁盤目錄和 JournalNode 集群上的共享目錄。
這些工作完成之后,就開(kāi)始執(zhí)行“基于 QJM 的共享存儲(chǔ)系統(tǒng)的數(shù)據(jù)恢復(fù)機(jī)制分析”一節(jié)所描述的流程,調(diào)用 FSEditLog 類的 recoverUnclosedStreams 方法讓 JournalNode 集群中各個(gè)節(jié)點(diǎn)上的 EditLog 達(dá)成一致。
然后調(diào)用 EditLogTailer 類的 catchupDuringFailover 方法從 JournalNode 集群上補(bǔ)齊落后的 EditLog。
最后打開(kāi)一個(gè)新的 EditLog Segment 用于新寫入數(shù)據(jù),同時(shí)啟動(dòng) Active NameNode 所需要的線程和服務(wù)。
- NameNode 從 Active 狀態(tài)切換為 Standby 狀態(tài)
當(dāng) NameNode 從 Active 狀態(tài)切換為 Standby 狀態(tài)的時(shí)候,首先需要做的就是停止它在 Active 狀態(tài)的時(shí)候啟動(dòng)的線程和服務(wù),然后關(guān)閉用于讀取本地磁盤目錄和 JournalNode 集群上的共享目錄的 EditLog 的 JournalSet。
接下來(lái)會(huì)調(diào)用 FSEditLog 的 initSharedJournalsForRead 方法重新打開(kāi)用于讀取 JournalNode 集群上的共享目錄的 JournalSet。
這些工作完成之后,就會(huì)啟動(dòng) EditLogTailer 線程和 StandbyCheckpointer 線程,EditLogTailer 線程會(huì)定時(shí)從 JournalNode 集群上同步 Edit Log。
參見(jiàn):
Hadoop NameNode 高可用 (High Availability) 實(shí)現(xiàn)解析:https://www.ibm.com/developerworks/cn/opensource/os-cn-hadoop-name-node/index.html
-
HDFS存儲(chǔ)策略?
講存儲(chǔ)策略之前,我想先補(bǔ)充下冷熱數(shù)據(jù)的概念
冷熱數(shù)據(jù)從訪問(wèn)頻次來(lái)看可分為:
熱數(shù)據(jù):是需要被計(jì)算節(jié)點(diǎn)頻繁訪問(wèn)的在線類數(shù)據(jù)。
冷數(shù)據(jù):是對(duì)于離線類不經(jīng)常訪問(wèn)的數(shù)據(jù),比如企業(yè)備份數(shù)據(jù)、業(yè)務(wù)與操作日志數(shù)據(jù)、話單與統(tǒng)計(jì)數(shù)據(jù)。熱數(shù)據(jù)就近計(jì)算,冷數(shù)據(jù)集中存儲(chǔ)
從數(shù)據(jù)分析層面來(lái)看可分:
冷數(shù)據(jù)、溫?cái)?shù)據(jù)和熱數(shù)據(jù)。
冷數(shù)據(jù)——性別、興趣、常住地、職業(yè)、年齡等數(shù)據(jù)畫像,表征“這是什么樣的人”;
溫?cái)?shù)據(jù)——近期活躍應(yīng)用、近期去過(guò)的地方等具有一定時(shí)效性的行為數(shù)據(jù),表征“最近對(duì)什么感興趣”;
熱數(shù)據(jù)——當(dāng)前地點(diǎn)、打開(kāi)的應(yīng)用等場(chǎng)景化明顯的、稍縱即逝的營(yíng)銷機(jī)會(huì),表征“正在哪里干什么”。
根據(jù)對(duì)數(shù)據(jù)的冷熱理解,我們能更好的規(guī)劃我們的hdfs存儲(chǔ)策略,那接下來(lái)主要講HDFS存儲(chǔ)策略的異構(gòu)存儲(chǔ)和副本放置兩個(gè)方面。
- 異構(gòu)存儲(chǔ)策略
Hadoop從2.6.0版本開(kāi)始支持異構(gòu)存儲(chǔ)功能。我們知道HDFS默認(rèn)的存儲(chǔ)策略,對(duì)于每個(gè)數(shù)據(jù)塊,采用三個(gè)副本的存儲(chǔ)方式,保存在不同節(jié)點(diǎn)的磁盤上。異構(gòu)存儲(chǔ)的作用在于利用服務(wù)器不同類型的存儲(chǔ)介質(zhì)(包括HDD硬盤、SSD、內(nèi)存等)提供更多的存儲(chǔ)策略(例如三個(gè)副本一個(gè)保存在SSD介質(zhì),剩下兩個(gè)仍然保存在HDD硬盤),從而使得HDFS的存儲(chǔ)能夠更靈活高效地應(yīng)對(duì)各種應(yīng)用場(chǎng)景。
HDFS中預(yù)定義支持的各種存儲(chǔ)包括:ARCHIVE:高存儲(chǔ)密度但耗電較少的存儲(chǔ)介質(zhì),例如磁帶,通常用來(lái)>存儲(chǔ)冷數(shù)據(jù)
DISK:磁盤介質(zhì),這是HDFS最早支持的存儲(chǔ)介質(zhì)
SSD:固態(tài)硬盤,是一種新型存儲(chǔ)介質(zhì),目前被不少互聯(lián)網(wǎng)公司使用
RAM_DISK :數(shù)據(jù)被寫入內(nèi)存中,同時(shí)會(huì)往該存儲(chǔ)介質(zhì)中再(異步)寫一份異構(gòu)存儲(chǔ)
HDFS中支持的存儲(chǔ)策略包括:
Lazy_persist:一個(gè)副本保存在內(nèi)存RAM_DISK中,其余副本保存在磁盤中
ALL_SSD:所有副本都保存在SSD中
One_SSD:一個(gè)副本保存在SSD中,其余副本保存在磁盤中
Hot:所有副本保存在磁盤中,這也是默認(rèn)的存儲(chǔ)策略
Warm:一個(gè)副本保存在磁盤上,其余副本保存在歸檔存儲(chǔ)上
Cold:所有副本都保存在歸檔存儲(chǔ)上一個(gè)存儲(chǔ)策略,包含以下部分
Policy ID --策略ID
Policy name --策略名稱
A list of storage types for block placement --塊存放的有關(guān)存儲(chǔ)類型(可以多個(gè))
A list of fallback storage types for file creation--如果創(chuàng)建失敗的替代存儲(chǔ)類型(可以多個(gè))
A list of fallback storage types for replication--如果復(fù)制失敗的替代存儲(chǔ)類型(可以多個(gè))異構(gòu)存儲(chǔ)當(dāng)有足夠空間的時(shí)候,塊復(fù)制使用下表中第三列所列出的存儲(chǔ)類型。
如果第三列的空間不夠,則考慮用第四列的(創(chuàng)建的時(shí)候)或者第五列的(復(fù)制的時(shí)候)配置:
dfs.storage.policy.enabled - 啟用/關(guān)閉存儲(chǔ)策略特性。默認(rèn)是true(開(kāi)啟)
dfs.datanode.data.dir - 數(shù)據(jù)路徑,多個(gè)以逗號(hào)分隔,但必須在前面帶上存儲(chǔ)類型。例如:A datanode storage location /grid/dn/ssd0 on SSD can should configured with [SSD]file:///grid/dn/ssd0 A datanode storage location /grid/dn/archive0 on ARCHIVE should be configured with [ARCHIVE]file:///grid/dn/archive0 A datanode storage location /grid/dn/ram0 on RAM_DISK should be configured with [RAM_DISK]file:///grid/dn/ram0
如果沒(méi)有設(shè)定存儲(chǔ)類型,那么使用默認(rèn)的([DISK])
總體上HDFS異構(gòu)存儲(chǔ)的價(jià)值在于,根據(jù)數(shù)據(jù)熱度采用不同策略從而提升集群整體資源使用效率。對(duì)于頻繁訪問(wèn)的數(shù)據(jù),將其全部或部分保存在更高訪問(wèn)性能的存儲(chǔ)介質(zhì)(內(nèi)存或SSD)上,提升其讀寫性能;對(duì)于幾乎不會(huì)訪問(wèn)的數(shù)據(jù),保存在歸檔存儲(chǔ)介質(zhì)上,降低其存儲(chǔ)成本。但是HDFS異構(gòu)存儲(chǔ)的配置需要用戶對(duì)目錄指定相應(yīng)的策略,即用戶需要預(yù)先知道每個(gè)目錄下的文件的訪問(wèn)熱度,在實(shí)際大數(shù)據(jù)平臺(tái)的應(yīng)用中,這是比較困難的一點(diǎn)
補(bǔ)充一點(diǎn),在hadoop3.0之后又引入了支持HDFS文件塊級(jí)別的糾刪碼,底層采用Reed-Solomon(k,m)算法。RS是一種常用的糾刪碼算法,通過(guò)矩陣運(yùn)算,可以為k位數(shù)據(jù)生成m位校驗(yàn)位,根據(jù)k和m的取值不同,可以實(shí)現(xiàn)不同程度的容錯(cuò)能力,是一種比較靈活的糾刪碼算法。
Reed-Solomon(k,m)
HDFS糾刪碼技術(shù)能夠降低數(shù)據(jù)存儲(chǔ)的冗余度,以RS(3,2)為例,其數(shù)據(jù)冗余度為67%,相比Hadoop默認(rèn)的200%大為減少。但是糾刪碼技術(shù)存儲(chǔ)數(shù)據(jù)和數(shù)據(jù)恢復(fù)都需要消耗cpu進(jìn)行計(jì)算,實(shí)際上是一種以時(shí)間換空間的選擇,因此比較適用的場(chǎng)景是對(duì)冷數(shù)據(jù)的存儲(chǔ)。冷數(shù)據(jù)存儲(chǔ)的數(shù)據(jù)往往一次寫入之后長(zhǎng)時(shí)間沒(méi)有訪問(wèn),這種情況下可以通過(guò)糾刪碼技術(shù)減少副本數(shù)。
- 副本放置策略
NameNode在挑選合適的DataNode去存儲(chǔ)Block的時(shí)候,不僅僅考慮了DataNode的存儲(chǔ)空間夠不夠,還會(huì)考慮這些DataNode在不在同一個(gè)機(jī)架上。
這就需要NameNode必須知道所有的DataNode分別位于哪個(gè)機(jī)架上(所以也稱為機(jī)架感知)。
當(dāng)然,默認(rèn)情況下NameNode是不會(huì)知道機(jī)架的存在的,也就是說(shuō),默認(rèn)情況下,NameNode會(huì)認(rèn)為所有的DataNode都在同一個(gè)機(jī)架上(/defaultRack)。
除非我們?cè)趆dfs-site.xml里面配置topology.script.file.name選項(xiàng),這個(gè)選項(xiàng)的值是一個(gè)可執(zhí)行文件的位置,而該只執(zhí)行文件的作用是將輸入的DataNode的ip地址按照一定規(guī)則計(jì)算,然后輸出它所在的機(jī)架的名字,如/rack1, /rack2之類。借助這個(gè)文件,NameNode就具備了機(jī)架感知了。當(dāng)它在挑選DataNode去存儲(chǔ)Block的時(shí)候,它會(huì)遵循以下原則:
- 1st replica. 如果寫請(qǐng)求方所在機(jī)器是其中一個(gè)個(gè)DataNode,則直接存放在本地,否則隨機(jī)在集群中選擇一個(gè)DataNode。
- 2nd replica. 第二個(gè)副本存放于不同第一個(gè)副本的所在的機(jī)架。
- 3rd replica. 第三個(gè)副本存放于第二個(gè)副本所在的機(jī)架,但是屬于不同的節(jié)點(diǎn)。
other replica. 如果還有更多的副本,則隨機(jī)放在節(jié)點(diǎn)中。
副本放置策略
參見(jiàn):
冷數(shù)據(jù)、溫?cái)?shù)據(jù)、熱數(shù)據(jù),難道數(shù)據(jù)也是有溫度的?:
http://www.lxweimin.com/p/053ba529bf02
如何根據(jù)數(shù)據(jù)冷熱程度分層存儲(chǔ),讓HDFS更高效?:http://dbaplus.cn/news-73-1615-1.html
Hadoop--HDFS之機(jī)架感知和副本策略:http://xiejm.com/Hadoop/Hadoop--HDFSreplication.html
HDFS的存儲(chǔ)策略:https://www.cnblogs.com/lzfhope/p/7068909.html
-
單獨(dú)說(shuō)說(shuō)Parquet列式存儲(chǔ):
首先強(qiáng)調(diào),paruqet是列式存儲(chǔ)(以前一直以為是行式存儲(chǔ),踩了好多坑,全是淚)。一開(kāi)始是由twitter和cloudera共同完成并開(kāi)源,后來(lái)被apache孵化為頂級(jí)項(xiàng)目。語(yǔ)言無(wú)關(guān),可以配合多種hadoop組件使用。
如上圖所示,parquet序列化和反序列化主要由三大部分組成:存儲(chǔ)格式(Parquet file format,定義了其內(nèi)部數(shù)據(jù)類型和存儲(chǔ)格式)、對(duì)象模型轉(zhuǎn)換器(Conerters,完成內(nèi)外部對(duì)象映射)和對(duì)象模型(object model,內(nèi)存中的數(shù)據(jù)表示)組成。其列式存儲(chǔ)是將某一列的數(shù)據(jù)連續(xù)存儲(chǔ),每一行中的不同列散列分布,這樣查詢的時(shí)候不用掃描全部數(shù)據(jù),而且因?yàn)榱惺峭瑯?gòu)數(shù)據(jù)可以進(jìn)行有效壓縮,所以能有效減少IO。
列式存儲(chǔ)還有兩個(gè)概念:映射下推、謂詞下推
映射下推(Project PushDown),是將以前關(guān)系型數(shù)據(jù)庫(kù)拿出所有數(shù)據(jù)后再篩選有用的字段,變?yōu)橹苯幼x取需要的列。而且parquet還能會(huì)考慮列是否連續(xù),可以一次性讀出多個(gè)列的數(shù)據(jù)到內(nèi)存。
謂詞下推(Predicate PushDown),是將where或on中的過(guò)濾條件盡可能的交給底層執(zhí)行,從而減少每層交互的數(shù)據(jù)量。parquet通過(guò)在每個(gè)列塊存儲(chǔ)的時(shí)候計(jì)算統(tǒng)計(jì)信息(最大最小值、空值個(gè)數(shù)),來(lái)判斷rowgroup是否需要掃描。
parquet數(shù)據(jù)模型如下所示:
message AddressBook {
required string owner;
repeated string ownerPhoneNumbers;
repeated group contacts {
required string name;
optional string phoneNumber;
}
}
根為message,message包含多個(gè)field,沒(méi)分field包含三個(gè)屬性:repetition、type、name,其中repetition的取值為required(必須1次)、optional(至多1次)或repeated(任意多次)。
parquet 沒(méi)有實(shí)現(xiàn)復(fù)雜的map、list、set,而是使用repeated field(實(shí)現(xiàn)set、list) 和 groups(key-value對(duì)groups來(lái)實(shí)現(xiàn)map)來(lái)實(shí)現(xiàn)。
parquet樹(shù)型結(jié)構(gòu)如下:
系統(tǒng)在存儲(chǔ)的時(shí)候只存葉子節(jié)點(diǎn),但會(huì)添加一些輔助字段來(lái)顯示結(jié)構(gòu)。
如:
其存儲(chǔ)結(jié)構(gòu)為:
其中r和d字段分別代表repetition level 和 definition level。
repetition level:只針對(duì)repeated field,指明其定義在哪一級(jí)(級(jí)數(shù)按repeated計(jì)算)重復(fù),且第一次出現(xiàn)因?yàn)椴淮嬖谥貜?fù),所以為0。
definition level:指明該列路徑上有多少可選(optional、repeated)field被定義。
最終converter通過(guò)stripping和assembly這些subfield實(shí)現(xiàn)序列化和反序列化。
parquet的文件格式如下:
parquet數(shù)據(jù)會(huì)按行組進(jìn)行切分(官方建議調(diào)整行組大小和HDFS塊大小到1G以實(shí)現(xiàn)最優(yōu)性能),每個(gè)行組包含擁有所有列數(shù)據(jù)的列塊,列塊中包含有分頁(yè)(官方建議8k),分頁(yè)為parquet壓縮和編碼的單元,不同的頁(yè)面允許使用不同的編碼方式,分頁(yè)對(duì)于數(shù)據(jù)模型透明。
性能方面,通過(guò)對(duì)比不同的存儲(chǔ)格式,parquet在數(shù)據(jù)壓縮率和查詢速度方面都有明顯優(yōu)勢(shì),相較于ORC可嵌套數(shù)據(jù)結(jié)構(gòu),但不支持?jǐn)?shù)據(jù)修改和ACID,所以更適用于OLAP領(lǐng)域,是一種工業(yè)界廣泛認(rèn)可的優(yōu)化方案。
如果說(shuō)HDFS是大數(shù)據(jù)時(shí)代文件系統(tǒng)的實(shí)際標(biāo)準(zhǔn)的話,parquet就是大數(shù)據(jù)時(shí)代存儲(chǔ)格式的實(shí)際標(biāo)準(zhǔn)。
參考http://www.lxweimin.com/p/47b39ae336d5。
TO BE CONTINUED ......