HDFS 底層交互原理,看這篇就夠了

前言

大家好,我是林哥!

HDFS全稱是 Hadoop Distribute File System,是 Hadoop 最重要的組件之一,也被稱爲分步式存儲之王。本文主要從 HDFS 高可用架構組成、HDFS 讀寫流程、如何保證可用性以及高頻面試題出發,提高大家對 HDFS 的認識,掌握一些高頻的 HDFS 面試題。本篇文章概覽如下圖:

本篇文章概覽

1.HA 架構組成

1.1HA 架構模型

在 HDFS 1.X 時,NameNode 是 HDFS 集羣中可能發生單點故障的節點,集羣中只有一個 NameNode,一旦 NameNode 宕機,整個集羣將處於不可用的狀態。

在 HDFS 2.X 時,HDFS 提出了高可用 (High Availability, HA) 的方案,解決了 HDFS 1.X 時的單點問題。在一個 HA 集羣中,會配置兩個 NameNode ,一個是 Active NameNode(主),一個是 Stadby NameNode(備)。主節點負責執行所有修改命名空間的操作,備節點則執行同步操作,以保證與主節點命名空間的一致性。HA 架構模型如下圖所示:

HA 架構組成 2

HA 集羣中所包含的進程的職責各不相同。爲了使得主節點和備用節點的狀態一致,採用了 Quorum Journal Manger (QJM)方案解決了主備節點共享存儲問題,如圖 JournalNode 進程,下面依次介紹各個進程在架構中所起的作用:

注意:QJM 方案是基於 Paxos 算法實現的,集羣由 2N + 1 JouranlNode 進程組成,最多可以容忍 N 臺 JournalNode 宕機,宕機數大於 N 臺,這個算法就失效了!

1.2HA 主備故障切換流程

HA 集羣剛啓動時,兩個 NameNode 節點狀態均爲 Standby,之後兩個 NameNode 節點啓動 ZKFC 進程後會去 ZooKeeper 集羣搶佔分步式鎖,成功獲取分步式鎖,ZooKeeper 會創建一個臨時節點,成功搶佔分步式鎖的 NameNode 會成爲 Active NameNode,ZKFC 便會實時監控自己的 NameNode。

HDFS 提供了兩種 HA 狀態切換方式:一種是管理員手動通過DFSHAAdmin -faieover執行狀態切換;另一種則是自動切換。下面分別從兩種情況分析故障的切換流程:

  1. 主 NameNdoe 宕機後,備用 NameNode 如何升級爲主節點?

    當主 NameNode 宕機後,對應的 ZKFC 進程檢測到 NameNode 狀態,便向 ZooKeeper 發生刪除鎖的命令,鎖刪除後,則觸發一個事件回調備用 NameNode 上的 ZKFC

    ZKFC 得到消息後先去 ZooKeeper 爭奪創建鎖,鎖創建完成後會檢測原先的主 NameNode 是否真的掛掉(有可能由於網絡延遲,心跳延遲),掛掉則升級備用 NameNode 爲主節點,沒掛掉則將原先的主節點降級爲備用節點,將自己對應的 NameNode 升級爲主節點。

  2. 主 NameNode 上的 ZKFC 進程掛掉,主 NameNode 沒掛,如何切換?

    ZKFC 掛掉後,ZKFC 和 ZooKeeper 之間 TCP 鏈接會隨之斷開,session 也會隨之消失,鎖被刪除,觸發一個事件回調備用 NameNode ZKFC,ZKFC 得到消息後會先去 ZooKeeper 爭奪創建鎖,鎖創建完成後也會檢測原先的主 NameNode 是否真的掛掉,掛掉則升級 備用 NameNode 爲主節點,沒掛掉則將主節點降級爲備用節點,將自己對應的 NameNode 升級爲主節點。

1.3Block、packet 及 chunk 概念

在 HDFS 中,文件存儲是按照數據塊(Block)爲單位進行存儲的,在讀寫數據時,DFSOutputStream使用 Packet 類來封裝一個數據包。每個 Packet 包含了若干個 chunk 和對應的 checksum。

  1. 源碼級讀寫流程

2.1HDFS 讀流程

HDFS 讀流程

我們以從 HDFS 讀取一個 information.txt 文件爲例,其讀取流程如上圖所示,分爲以下幾個步驟:

  1. 打開 information.txt 文件:首先客戶端調用 DistributedFileSystem.open() 方法打開文件,這個方法在底層會調用DFSclient.open() 方法,該方法會返回一個 HdfsDataInputStream 對象用於讀取數據塊。但實際上真正讀取數據的是 DFSInputStream ,而 HdfsDataInputStream 是 DFSInputStream 的裝飾類(new HdfsDataInputStream(DFSInputStream))。

  2. 從 NameNode 獲取存儲 information.txt 文件數據塊的 DataNode 地址:即獲取組成 information.txt block 塊信息。在構造輸出流 DFSInputStream 時,會通過調用 getBlockLocations() 方法向 NameNode 節點獲取組成 information.txt 的 block 的位置信息,並且 block 的位置信息是按照與客戶端的距離遠近排好序。

  3. 連接 DataNode 讀取數據塊: 客戶端通過調用 DFSInputStream.read() 方法,連接到離客戶端最近的一個 DataNode 讀取 Block 塊,數據會以數據包(packet)爲單位從 DataNode 通過流式接口傳到客戶端,直到一個數據塊讀取完成;DFSInputStream會再次調用 getBlockLocations() 方法,獲取下一個最優節點上的數據塊位置。

  4. 直到所有文件讀取完成,調用 close() 方法,關閉輸入流,釋放資源。

從上述流程可知,整個過程最主要涉及到 open()read()兩個方法(其它方法都是在這兩個方法的調用鏈中調用,如getBlockLocations()),下面依次介紹這 2 個方法的實現。

注:本文是以 hadoop-3.1.3 源碼爲基礎!

總結:HDFS 讀取一個文件,調用流程如下:(中間涉及到的部分方法未列出)

usercode 調用 open()  --->  DistributedFileSystem.open() ---> DFSClient.open() ---> 返回一個 DFSInputStream 對象給 DistributedFileSystem ---> new hdfsDataInputStream(DFSInputStream)  並返回給用戶;

usercode 調用 read()   ---> 底層DFSIputStream.read() ---> readWithStrategy(bytArrayReader)

2.2HDFS 寫流程

介紹完  HDFS 讀的流程,接下來看看一個文件的寫操作的實現。從下圖中可以看出,HDFS 寫流程涉及的方法比較多,過程也比較複雜。

1. 在 namenode 創建文件: 當 client 寫一個新文件時,首先會調用 DistributeedFileSytem.creat() 方法,DistributeFileSystem 是客戶端創建的一個對象,在收到 creat 命令之後,DistributeFileSystem 通過 RPC 與 NameNode 通信,讓它在文件系統的 namespace 創建一個獨立的新文件;namenode 會先確認文件是否存在以及客戶端是否有權限,確認成功後,會返回一個 HdfsDataOutputStream 對象,與讀流程類似,這個對象底層包裝了一個 DFSOutputStream 對象,它纔是寫數據的真正執行者。

2. 建立數據流 pipeline 管道: 客戶端得到一個輸出流對象,還需要通過調用 ClientProtocol.addBlock()向 namenode 申請新的空數據塊,addBlock( ) 會返回一個 LocateBlock 對象,該對象保存了可寫入的 DataNode 的信息,並構成一個 pipeline,默認是有三個 DataNode 組成。

3. 通過數據流管道寫數據:DFSOutputStream調用 write()方法把數據寫入時,數據會先被緩存在一個緩衝區中,寫入的數據會被切分成多個數據包,每當達到一個數據包長度(默認 65536 字節)時,

DFSOutputStream會構造一個 Packet 對象保存這個要發送的數據包;新構造的 Packet 對象會被放到 DFSOutputStream維護的 dataQueue 隊列中,DataStreamer 線程會從 dataQueue 隊列中取出 Packet 對象,通過底層 IO 流發送到 pipeline 中的第一個 DataNode,然後繼續將所有的包轉到第二個 DataNode 中,以此類推。發送完畢後,

這個 Packet 會被移出 dataQueue,放入 DFSOutputStream 維護的確認隊列 ackQueue 中,該隊列等待下游 DataNode 的寫入確認。當一個包已經被 pipeline 中所有的 DataNode 確認了寫入磁盤成功,這個數據包纔會從確認隊列中移除。

4. 關閉輸入流並提交文件: 當客戶端完成了整個文件中所有的數據塊的寫操作之後,會調用 close() 方法關閉輸出流,客戶端還會調用 ClientProtoclo.complete() 方法通知 NameNode 提交這個文件中的所有數據塊,

NameNode 還會確認該文件的備份數是否滿足要求。對於 DataNode 而言,它會調用 blockReceivedAndDelete() 方法向 NameNode 彙報,NameNode 會更新內存中的數據塊與數據節點的對應關係。

從上述流程來看,整個寫流程主要涉及到了 creat()write()這些方法,下面着重介紹下這兩個方法的實現。當調用 DistributeedFileSytem.creat() 方法時,其底層調用的其實是 DFSClient.create()方法,其源碼如下:

3.HDFS 如何保證可用性?

在 1.1 節中已經闡述了 HDFS 的高可用的架構,分別涉及到 NameNodeDataNode,Journalnode,ZKFC等組件。所以,在談及 HDFS 如何保證可用性,要從多個方面去回答。

4.HDFS 高頻面試題

你好,我是林哥,某雙一流研究生,互聯網大廠大數據工程師,目前杭漂!非科班逆襲大廠!

同時,我也是視頻號玩家:小林玩大數據!分享大數據知識,逆襲經驗!歡迎圍觀!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/26pnTa2WvidbSM_balvxpQ