Hadoop HDFS 數據讀寫流程分析

本文轉載自《Hadoop HDFS 數據讀寫流程分析》,該文對Hdfs的讀寫流程分析之簡潔明瞭,令人歎服。

HDFS的數據模型

在對讀寫流程進行分析之前,我們需要先對 HDFS 的數據模型有一個簡單的瞭解。


如上圖所示,在 NameNode 中有一個唯一的 FSDirectory 類負責維護文件系統的節點關係。文件系統中的每個路徑會被抽象爲一個 INode 對象。在 FSDirectory 中有一個叫做 rootDir 的 INodeDirectory 類,繼承自 INode 類,它代表着整個文件系統的根節點。

常用的 INode 節點有 INodeDirectory, INodeFile, INodeReference 三種。

  • INodeDirectory 類代表着對目錄對象的抽象,在類中有一個 List<inode> 對象 children 負責保存當前節點的子節點信息。</inode>

  • INodeFile 類代表着對文件對象的抽象,對於一個大文件, HDFS 可能將其拆分爲多個小文件進行存儲,在這裏的 blocks 對象是一個數據對象,代表着小文件的具體存放位置信息。

  • INodeReference 類可以理解成 Unix 系統中的硬鏈接。當文件系統中可能出現多個 path 地址對應同一個 INode 節點時,會構造出 INodeReference 對象。
    例如我們對 /abc/foo 構造一個快照 s0, 則 然後將 /abc/foo mv 到另一個路徑 /xyz/bar,此時 /xyz/bar 和 /abc/.snapshot/s0/foo 雖然是不同的路徑,但是對應着同一個 block 地址。

HDFS的IO操作

當通過客戶端 hdfs dfs 命令進行文件 IO 操作時,會根據配置文件中 fs.defaultFS 的配置信息構造出一個 FileSystem 對象。具體的文件操作指令,通過 FileSystem 中對應的接口進行訪問。

對於客戶端HDFS操作,他的默認 FileSystem 實現類是 DistributedFileSystem, 在 DistribtedFileSystem 中有一個 DFSClient 對象。這個對象使用前一篇文章中介紹的內部 RPC 通信機制,構造了一個 NameNode 的代理對象,負責同 NameNode 間進行 RPC 操作。

HDFS的文件寫入流程

以 hadoop fs -put 操作爲例:

  • 當接收到 PUT 請求時,嘗試在 NameNode 中 create 一個新的 INode 節點,這個節點是根據 create 中發送過去的 src 路徑構建出的目標節點,如果發現節點已存在或是節點的 parent 存在且不爲 INodeDirectory 則異常中斷,否則則返回包含 INode 信息的 HdfsFileStatus 對象。

  • 使用 HdfsFileStatus 構造一個實現了 OutputStream 接口的 DFSOutputStream 類,通過 nio 接口將需要傳輸的數據寫入 DFSOutputStream。

  • 在 DFSOutputStream 中寫入的數據被以一定的 size(一般是 64 k)封裝成一個 DFSPacket,壓入 DataStreamer 的傳輸隊列中。

  • DataStreamer 是 Client 中負責數據傳輸的獨立線程,當發現隊列中有 DFSPacket 時,先通過 namenode.addBlock 從 NameNode 中獲取可供傳輸的 DataNode 信息,然後同指定的 DataNode 進行數據傳輸。

  • DataNode 中有一個專門的 DataXceiverServer 負責接收數據,當有數據到來時,就進行對應的 writeBlock 寫入操作,同時如果發現還有下游的 DataNode 同樣需要接收數據,就通過管道再次將發來的數據轉發給下游 DataNode,實現數據的備份,避免通過 Client 一次進行數據發送。

整個操作步驟中的關鍵步驟有 NameNode::addBlock 以及 DataNode::writeBlock, 接下來會對這兩步進行詳細分析。

NameNode::addBlock解析

在上面的數據模型中我們看到,對於一個 INodeFile 節點,我們可能會根據其數據大小將其拆分成多個 Block,因此當傳輸新文件或者文件傳輸尺寸已經超過 blockSize 的時候,就需要通過 addBlock 獲取新的傳輸地址。

NameNode 中 addBlock 的實現路徑在 FSNamesystem::getAdditionalBlock 中,這裏先通過 FSDirWriteFileOp::validateAddBlock 判斷是否是因爲延遲或異常問題導致的無效請求,如果不是,則通過 FSDirWriteFileOp.chooseTargetForNewBlock 選取新 Block 的目標 DataNode,

chooseTargetForNewBlock 的具體算法由 BlockPlacementPolicy 完成,默認情況下會優先選擇 client 自身所在機器作爲 target,如果自身機器不是 DataNode,則會優先選擇和當前機器處於同一機架( rack )中的 DataNode,以提升數據傳輸效率。

官方放置策略解釋:

  • The 1st replica is placed on the local machine, otherwise a random datanode.
  • The 2nd replica is placed on a datanode that is on a different rack.
  • The 3rd replica is placed on a datanode which is on a different node of the rack as the second replica.

確定寫入的 DataNode 後,通過 FSDirWriteFileOp::storeAllocatedBlock 構造 Block 對象,並放入 src 對應的 INodeFile 中。

DataNode::writeBlock 解析

DataNode 中的 DataXceiverServer 負責接收從 Client 發送來的數據傳輸請求。當有新的鏈接接通時,會構造一個 DataXceiver 線程進行數據接收。

在 DataXceiver::writeBlock 中,如果發現 targets.length > 0,則說明還有下游的 DataNode 需要接收數據傳輸,這時候會和 Client 一樣構造出一個鏈接到下游 DataNode 的 socket 鏈接,通過 new Sender(mirrorOut).writeBlock 將數據寫入下游。

HDFS的文件讀取流程

hadoop fs -get操作:

GET 操作的流程,相對於 PUT 會比較簡單,先通過參數中的來源路徑從 NameNode 對應 INode 中獲取對應的 Block 位置,然後基於返回的 LocatedBlocks 構造出一個 DFSInputStream 對象。在 DFSInputStream 的 read 方法中,根據 LocatedBlocks 找到擁有 Block 的 DataNode 地址,通過 readBlock 從 DataNode 獲取字節流。

hadoop fs -mv 操作:

MV 操作只涉及對文件名稱或路徑的更改,因此他的主要步驟集中在 NameNode 端,Client 端只是通過 RPC 調用 NameNode::rename

從活動圖中我們看到,整個 rename 的操作分了兩步,第一步是 removeSrc4OldRename,將 src 從 FSDirectory 中移除,第二步是 addSourceToDestination ,將之前移除的 src 的 INode,重新根據 dst 的路徑添加到 FSDirectory 中,完成整個重命名流程。

總結

HDFS 中的文件 IO 操作主要是發生在 Client 和 DataNode 中。

NameNode 作爲整個文件系統的 Namesystem 負責管理整個文件系統的路徑樹,當需要新建文件或讀取文件時,會從文件樹中讀取對應的路徑節點的 Block 信息,發送回 Client 端。 Client 通過從返回數據中得到的 DataNode 和 Block 信息,直接從 DataNode 中進行數據讀取。

整個數據 IO 流程中,NameNode 只負責管理節點和 DataNode 的對應關係,涉及到 IO 操作的行爲少,從而將整個文件傳輸壓力從 NameNode 轉移到了 DataNode 中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章