Hdfs 的數(shù)據(jù)模型
在對讀寫流程進行分析之前,我們需要先對 Hdfs 的數(shù)據(jù)模型有一個簡單的認知祷蝌。
如上圖所示害幅,在 NameNode 中有一個唯一的 FSDirectory 類負責維護文件系統(tǒng)的節(jié)點關系练慕。文件系統(tǒng)中的每個路徑會被抽象為一個 INode 對象。在 FSDirectory 中有一個叫做 rootDir 的 INodeDirectory 類砂吞,繼承自 INode 類,它代表著整個文件系統(tǒng)的根節(jié)點崎溃。
常用的 INode 節(jié)點有 INodeDirectory, INodeFile, INodeReference 三種蜻直。
- INodeDirectory 類代表著對目錄對象的抽象,在類中有一個 List<INode> 對象 children 負責保存當前節(jié)點的子節(jié)點信息袁串。
- INodeFile 類代表著對文件對象的抽象概而,對于一個大文件, Hdfs 可能將其拆分為多個小文件進行存儲囱修,在這里的 blocks 對象是一個數(shù)據(jù)對象赎瑰,代表著小文件的具體存放位置信息。
- INodeReference 類可以理解成 Unix 系統(tǒng)中的硬鏈接破镰。當文件系統(tǒng)中可能出現(xiàn)多個 path 地址對應同一個 INode 節(jié)點時餐曼,會構(gòu)造出 INodeReference 對象。例如我們對 /abc/foo 構(gòu)造一個快照 s0, 則 然后將 /abc/foo mv 到另一個路徑 /xyz/bar啤咽,此時 /xyz/bar 和 /abc/.snapshot/s0/foo 雖然是不同的路徑晋辆,但是對應著同一個 block 地址。
Hdfs 的 IO 操作
當通過 hdfs dfs
進行文件 IO 操作時宇整,會根據(jù)配置文件中 fs.defaultFS
的配置信息構(gòu)造出一個 FileSystem
對象瓶佳。具體的文件操作指令,通過 FileSystem
中對應的接口進行訪問鳞青。
對于 hdfs 而言霸饲,他的默認 FileSystem
實現(xiàn)類是 DistributedFileSystem
, 在 DistribtedFileSystem
中有一個 DFSClient 對象为朋。這個對象使用前一篇文章中介紹的內(nèi)部 RPC 通信機制,構(gòu)造了一個 namenode 的代理對象厚脉,負責同 NameNode 間進行 RPC 操作习寸。
Hdfs 的文件寫入流程
以 PUT 操作為例:
- 當接收到 PUT 請求時,嘗試在 NameNode 中 create 一個新的 INode 節(jié)點傻工,這個節(jié)點是根據(jù) create 中發(fā)送過去的 src 路徑構(gòu)建出的目標節(jié)點,如果發(fā)現(xiàn)節(jié)點已存在或是節(jié)點的 parent 存在且不為 INodeDirectory 則異常中斷霞溪,否則則返回包含 INode 信息的 HdfsFileStatus 對象。
- 使用 HdfsFileStatus 構(gòu)造一個實現(xiàn)了 OutputStream 接口的
DFSOutputStream
類中捆,通過 nio 接口將需要傳輸?shù)臄?shù)據(jù)寫入DFSOutputStream
鸯匹。 - 在
DFSOutputStream
中寫入的數(shù)據(jù)被以一定的 size(一般是 64 k)封裝成一個DFSPacket
,壓入DataStreamer
的傳輸隊列中。 -
DataStreamer
是 Client 中負責數(shù)據(jù)傳輸?shù)莫毩⒕€程泄伪,當發(fā)現(xiàn)隊列中有DFSPacket
時殴蓬,先通過namenode.addBlock
從NameNode
中獲取可供傳輸?shù)?DataNode
信息,然后同指定的DataNode
進行數(shù)據(jù)傳輸蟋滴。 -
DataNode
中有一個專門的DataXceiverServer
負責接收數(shù)據(jù)染厅,當有數(shù)據(jù)到來時,就進行對應的writeBlock
寫入操作津函,同時如果發(fā)現(xiàn)還有下游的DataNode
同樣需要接收數(shù)據(jù)肖粮,就通過管道再次將發(fā)來的數(shù)據(jù)轉(zhuǎn)發(fā)給下游DataNode
,實現(xiàn)數(shù)據(jù)的備份球散,避免通過 Client 一次進行數(shù)據(jù)發(fā)送尿赚。
整個操作步驟中的關鍵步驟有 NameNode::addBlock
以及 DataNode::writeBlock
, 接下來會對這兩步進行詳細分析。
NameNode::addBlock
解析
在上面的數(shù)據(jù)模型中我們看到蕉堰,對于一個 INodeFile 節(jié)點凌净,我們可能會根據(jù)其數(shù)據(jù)大小將其拆分成多個 Block
,因此當傳輸新文件或者文件傳輸尺寸已經(jīng)超過 blockSize 的時候屋讶,就需要通過 addBlock 獲取新的傳輸?shù)刂贰?/p>
NameNode 中 addBlock 的實現(xiàn)路徑在 FSNamesystem::getAdditionalBlock
中冰寻,這里先通過 FSDirWriteFileOp::validateAddBlock
判斷是否是因為延遲或異常問題導致的無效請求,如果不是皿渗,則通過 FSDirWriteFileOp.chooseTargetForNewBlock
選取新 Block
的目標 DataNode斩芭,
chooseTargetForNewBlock 的具體算法由 BlockPlacementPolicy
完成,默認情況下會優(yōu)先選擇 client 自身所在機器作為 target乐疆,如果自身機器不是 DataNode划乖,則會優(yōu)先選擇和當前機器處于同一機架( rack )中的 DataNode,以提升數(shù)據(jù)傳輸效率挤土。
確定寫入的 DataNode 后琴庵,通過 FSDirWriteFileOp::storeAllocatedBlock
構(gòu)造 Block 對象,并放入 src 對應的 INodeFile 中。
DataNode::writeBlock
解析
DataNode 中的 DataXceiverServer
負責接收從 Client 發(fā)送來的數(shù)據(jù)傳輸請求迷殿。當有新的鏈接接通時儿礼,會構(gòu)造一個 DataXceiver
線程進行數(shù)據(jù)接收。
在 DataXceiver::writeBlock
中庆寺,如果發(fā)現(xiàn) targets.length > 0
蚊夫,則說明還有下游的 DataNode 需要接收數(shù)據(jù)傳輸,這時候會和 Client 一樣構(gòu)造出一個鏈接到下游 DataNode 的 socket 鏈接懦尝,通過 new Sender(mirrorOut).writeBlock
將數(shù)據(jù)寫入下游知纷。
Hdfs 的文件讀取流程
GET 操作的流程,相對于 PUT 會比較簡單陵霉,先通過參數(shù)中的來源路徑從 NameNode 對應 INode 中獲取對應的 Block 位置屈扎,然后基于返回的 LocatedBlocks 構(gòu)造出一個 DFSInputStream 對象。在 DFSInputStream 的 read 方法中撩匕,根據(jù) LocatedBlocks 找到擁有 Block 的 DataNode 地址,通過 readBlock 從 DataNode 獲取字節(jié)流墨叛。
Hdfs 的文件重命名流程
MV 操作只涉及對文件名稱或路徑的更改止毕,因此他的主要步驟集中在 NameNode 端,Client 端只是通過 RPC 調(diào)用 NameNode::rename
從活動圖中我們看到漠趁,整個 rename 的操作分了兩步扁凛,第一步是 removeSrc4OldRename
,將 src 從 FSDirectory 中移除闯传,第二步是 addSourceToDestination
谨朝,將之前移除的 src 的 INode,重新根據(jù) dst 的路徑添加到 FSDirectory 中甥绿,完成整個重命名流程字币。
總結(jié)
HDFS 中的文件 IO 操作主要是發(fā)生在 Client 和 DataNode 中。
NameNode 作為整個文件系統(tǒng)的 Namesystem 負責管理整個文件系統(tǒng)的路徑樹共缕,當需要新建文件或讀取文件時洗出,會從文件樹中讀取對應的路徑節(jié)點的 Block 信息,發(fā)送回 Client 端图谷。 Client 通過從返回數(shù)據(jù)中得到的 DataNode 和 Block 信息翩活,直接從 DataNode 中進行數(shù)據(jù)讀取。
整個數(shù)據(jù) IO 流程中便贵,NameNode 只負責管理節(jié)點和 DataNode 的對應關系菠镇,涉及到 IO 操作的行為少,從而將整個文件傳輸壓力從 NameNode 轉(zhuǎn)移到了 DataNode 中承璃。