從HDFS的應用層面來看,咱們能夠很是容易的使用其API來操做HDFS,實現目錄的建立、刪除,文件的上傳下載、刪除、追加(Hadoop2.x版本之後開始支持)等功能。然而僅僅侷限與代碼層面是不夠的,瞭解其實現的具體細節和過程是頗有必要的,本文筆者給你們從如下幾個方面進行剖析:緩存
下面開始今天的內容分享。網絡
在HDFS上實現文件的建立,該過程並不複雜,Client到NameNode的相關操做,如:修改文件名,建立文件目錄或是子目錄等,而這些操做只涉及Client和NameNode的交互,過程以下圖所示:併發
咱們很熟悉,在咱們使用Java 的API去操做HDFS時,咱們會在Client端經過調用HDFS的FileSystem實例,去完成相應的功能,這裏咱們不討論FileSystem和DistributedFileSystem和關係,留在之後在閱讀這部分源碼的時候在去細說。而咱們知道,在使用API實現建立這一功能時是很是方便的,代碼以下所示:函數
public static void mkdir(String remotePath) throws IOException { FileSystem fs = FileSystem.get(conf); Path path = new Path(remotePath); fs.create(path); fs.close(); }
在代碼層面上,咱們只須要獲取操做HDFS的實例便可,調用其建立方法去實現目錄的建立。可是,其中的實現細節和相關步驟,咱們是須要清楚的。在咱們使用HDFS的FileSystem實例時,DistributedFileSystem對象會經過IPC協議調用NameNode上的mkdir()方法,讓NameNode執行具體的建立操做,在其指定的位置上建立新的目錄,同時記錄該操做並持久化操做記錄到日誌當中,待方法執行成功後,mkdir()會返回true表示建立成功來結束建立過程。在此期間,Client和NameNode不須要和DataNode進行數據交互。oop
在執行建立的過程中不涉及DataNode的數據交互,而在執行一些較爲複雜的操做時,如刪除,讀、寫操做時,須要DataNode來配合完成對應的工做。下面以刪除HDFS的文件爲突破口,來給你們展開介紹。刪除流程圖以下所示:學習
在使用API操做刪除功能時,我使用如下代碼,輸入要刪除的目錄地址,而後就發現HDFS上咱們所指定的刪除目錄就被刪除了,而然其中的刪除細節和過程卻並不必定清楚,刪除代碼以下所示:spa
public static void rmr(String remotePath) throws IOException { FileSystem fs = FileSystem.get(conf); Path path = new Path(remotePath); boolean status = fs.delete(path, true); LOG.info("Del status is [" + status + "]"); fs.close(); }
經過閱讀這部分刪除的API實現代碼,代碼很簡單,調用刪除的方法便可完成刪除功能。但它是如何完成刪除的,下面就爲你們這剖析一下這部份內容。日誌
在NameNode執行delete()方法時,它只是標記即將要刪除的Block(操做刪除的相關記錄是被記錄並持久化到日誌當中的,後續的相關HDFS操做都會有此記錄,便再也不提醒),NameNode是被動服務的,它不會主動去聯繫保存這些數據的Block的DataNode來當即執行刪除。而咱們能夠從上圖中發現,在DataNode向NameNode發送心跳時,在心跳的響應中,NameNode會經過DataNodeCommand來命令DataNode執行刪除操做,去刪除對應的Block。而在刪除時,須要注意,整個過程中,NameNode不會主動去向DataNode發送IPC調用,DataNode須要完成數據刪除,都是經過DataNode發送心跳獲得NameNode的響應,獲取DataNodeCommand的執行命令。code
在讀取HDFS上的文件時,Client、NameNode以及DataNode都會相互關聯。按照必定的順序來實現讀取這一過程,讀取過程以下圖所示:對象
經過上圖,讀取HDFS上的文件的流程能夠清晰的知道,Client經過實例打開文件,找到HDFS集羣的具體信息(咱們須要操做的是ClusterA,仍是ClusterB,須要讓Client端知道),這裏會建立一個輸入流,這個輸入流是鏈接DataNode的橋樑,相關數據的讀取Client都是使用這個輸入流來完成的,而在輸入流建立時,其構造函數中會經過一個方法來獲取NameNode中DataNode的ID和Block的位置信息。Client在拿到DataNode的ID和Block位置信息後,經過輸入流去讀取數據,讀取規則按照「就近原則」,即:和最近的DataNode創建聯繫,Client反覆調用read方法,並將讀取的數據返回到Client端,在達到Block的末端時,輸入流會關閉和該DataNode的鏈接,經過向NameNode獲取下一個DataNode的ID和Block的位置信息(若對象中爲緩存Block的位置信息,會觸發此步驟,不然略過)。而後拿到DataNode的ID和Block的位置信息後,在此鏈接最佳的DataNode,經過此DataNode的讀數據接口,來獲取數據。
另外,每次經過向NameNode回去Block信息並不是一次性獲取全部的Block信息,需得屢次經過輸入流向NameNode請求,來獲取下一組Block得位置信息。然而這一過程對於Client端來講是透明的,它並不關係是一次獲取仍是屢次獲取Block的位置信息,Client端在完成數據的讀取任務後,會經過輸入流的close()方法來關閉輸入流。
在讀取的過程中,有可能發生異常,如:節點掉電、網絡異常等。出現這種狀況,Client會嘗試讀取下一個Block的位置,同時,會標記該異常的DataNode節點,放棄對該異常節點的讀取。另外,在讀取數據的時候會校驗數據的完整性,若出現校驗錯誤,說明該數據的Block已損壞,已損壞的信息會上報給NameNode,同時,會從其餘的DataNode節點讀取相應的副本內容來完成數據的讀取。Client端直接聯繫NameNode,由NameNode分配DataNode的讀取ID和Block信息位置,NameNode不提供數據,它只處理Block的定位請求。這樣,防止因爲Client的併發數據量的迅速增長,致使NameNode成爲系統「瓶頸」(磁盤IO問題)。
HDFS的寫文件過程較於建立、刪除、讀取等,它是比較複雜的一個過程。下面,筆者經過一個流程圖來爲你們剖析其中的細節,以下圖所示:
Client端經過實例的create方法建立文件,同時實例建立輸出流對象,並經過遠程調用,通知NameNode執行建立命令,建立一個新文件,執行此命令須要進行各類校驗,如NameNode是否處理Active狀態,被建立的文件是否存在,Client建立目錄的權限等,待這些校驗都經過後,NameNode會建立一個新文件,完成整個此過程後,會經過實例將輸出流返回給Client。
這裏,咱們須要明白,在向DataNode寫數據的時候,Client須要知道它須要知道自身的數據要寫往何處,在茫茫Cluster中,DataNode成百上千,寫到DataNode的那個Block塊下,是Client須要清楚的。在經過create建立一個空文件時,輸出流會向NameNode申請Block的位置信息,在拿到新的Block位置信息和版本號後,輸出流就能夠聯繫DataNode節點,經過寫數據流創建數據流管道,輸出流中的數據被分紅一個個文件包,並最終打包成數據包發往數據流管道,流經管道上的各個DataNode節點,並持久化。
Client在寫數據的文件副本默認是3份,換言之,在HDFS集羣上,共有3個DataNode節點會保存這份數據的3個副本,客戶端在發送數據時,不是同時發往3個DataNode節點上寫數據,而是將數據先發送到第一個DateNode節點,而後,第一個DataNode節點在本地保存數據,同時推送數據到第二個DataNode節點,依此類推,直到管道的最後一個DataNode節點,數據確認包由最後一個DataNode產生,並逆向回送給Client端,在沿途的DataNode節點在確認本地寫入成功後,纔會往本身的上游傳遞應答信息包。這樣作的好處總結以下:
另外,在寫完一個Block後,DataNode節點會經過心跳上報本身的Block信息,並提交Block信息到NameNode保存。當Client端完成數據的寫入以後,會調用close()方法關閉輸出流,在關閉以後,Client端不會在往流中寫數據,於是,在輸出流都收到應答包後,就能夠通知NameNode節點關閉文件,完成一次正常的寫入流程。
在寫數據的過程中,也是有可能出現節點異常。然而這些異常信息對於Client端來講是透明的,Client端不會關心寫數據失敗後DataNode會採起哪些措施,可是,咱們須要清楚它的處理細節。首先,在發生寫數據異常後,數據流管道會被關閉,在已經發送到管道中的數據,可是尚未收到確認應答包文件,該部分數據被從新添加到數據流,這樣保證了不管數據流管道的哪一個節點發生異常,都不會形成數據丟失。而當前正常工做的DateNode節點會被賦予新的版本號,並通知NameNode。即便,在故障節點恢復後,上面只有部分數據的Block會由於Blcok的版本號與NameNode保存的版本號不一致而被刪除。以後,在從新創建新的管道,並繼續寫數據到正常工做的DataNode節點,在文件關閉後,NameNode節點會檢測Block的副本數是否達標,在未達標的狀況下,會選擇一個新的DataNode節點並複製其中的Block,建立新的副本。這裏須要注意的是,DataNode節點出現異常,只會影響一個Block的寫操做,後續的Block寫入不會收到影響。
前面說過,NameNode和DataNode之間數據交互,是經過DataNode節點向NameNode節點發送心跳來獲取NameNode的操做指令。心跳發送以前,DataNode須要完成一些步驟以後,才能發送心跳,流程圖以下所示:
從上圖來看,首先須要向NameNode節點發送校驗請求,檢測是否NameNode節點和DataNode節點的HDFS版本是否一致(有可能NameNode的版本爲2.6,DataNode的版本爲2.7,因此第一步須要校驗版本)。在版本校驗結束後,須要向NameNode節點註冊,這部分的做用是檢測該DataNode節點是否屬於NameNode節點管理的成員之一,換言之,ClusterA的DataNode節點不能直接註冊到ClusterB的NameNode節點上,這樣保證了整個系統的數據一致性。在完成註冊後,DataNode節點會上報本身所管理的全部的Block信息到NameNode節點,幫助NameNode節點創建HDFS文件Block到DataNode節點映射關係(即保存Meta),在完成此流程以後,纔會進入到心跳上報流程。
另外,若是NameNode節點長時間接收不到DataNode節點到心跳,它會認爲該DataNode節點的狀態處理Dead狀態。若是NameNode有些命令須要DataNode配置操做(如:前面的刪除指令),則會經過心跳後的DataNodeCommand這個返回值,讓DataNode去執行相關指令。
簡而言之,關於HDFS的建立、刪除、讀取以及寫入等流程,能夠一言以蔽之,內容以下:
這篇博客就和你們分享到這裏,若是你們在研究學習的過程中有什麼問題,能夠加羣進行討論或發送郵件給我,我會盡我所能爲您解答,與君共勉!