天天看點

HDFS追本溯源:HDFS操作的邏輯流程與源碼解析1. Client和NN2. Client讀檔案3. Client寫檔案4. DataNode的啟動與心跳機制5. SNN節點的中繼資料合并

本文主要介紹5個典型的hdfs流程,這些流程充分展現了hdfs實體間ipc接口和stream接口之間的配合。

      client到nn有大量的中繼資料操作,比如修改檔案名,在給定目錄下建立一個子目錄,這些操作一般隻涉及client和nn的互動,通過ipc調用clientprotocol進行。建立子目錄的邏輯流程如下圖:

HDFS追本溯源:HDFS操作的邏輯流程與源碼解析1. Client和NN2. Client讀檔案3. Client寫檔案4. DataNode的啟動與心跳機制5. SNN節點的中繼資料合并

      從圖中可見,建立子目錄這種操作并沒有涉及dn。因為中繼資料會被nn持久化到edits中,是以在持久化結束之後,這個調用就會被成功傳回。複習一下:nn維護了hdfs的檔案系統目錄樹和檔案與資料塊的對應關系,和資料塊與dn的對應關系。是以,建立目錄僅僅是在nn上也就很容易了解了。

     一些更為複雜的操作,如使用

來增加檔案的副本數,再如通過

來删除hdfs上的檔案,都需要dn配合執行一些動作。其中distributedfilesystem源碼在hadoop-hdfs-project\hadoop-hdfs\src\main\java\org\apache\hadoop\hdfs\distributedfilesystem.java

    以用戶端删除hdfs檔案為例,操作在nn上執行完成後,dn存放的檔案内容的資料塊也必須删除。但是,nn在執行delete()方法時,它隻标記需要删除的資料塊(當然,delete的記錄檔也會被持久化),而不會主動聯系dn去立即删除這些資料。當儲存着這些資料塊的dn在向nn發送心跳時,nn會通過心跳應答攜帶datanodecommand指令來通知dn删除資料。也就是說,被删除的資料塊在client接到成功的響應後,會在一段時間後才能真正删除,nn和dn永遠隻維護簡單的主從關系。nn永遠不會主動發起向dn的調用。nn隻能通過dn心跳應答中攜帶datanodecommand的指令對dn進行管理。

HDFS追本溯源:HDFS操作的邏輯流程與源碼解析1. Client和NN2. Client讀檔案3. Client寫檔案4. DataNode的啟動與心跳機制5. SNN節點的中繼資料合并

     使用java api讀取檔案的源碼如下:

    下圖顯示了hdfs在讀取檔案時,client,nn和dn發生的事件和這些事件的順序:

HDFS追本溯源:HDFS操作的邏輯流程與源碼解析1. Client和NN2. Client讀檔案3. Client寫檔案4. DataNode的啟動與心跳機制5. SNN節點的中繼資料合并

步驟1的源碼:

可見open傳回的是hdfsdatainputstream。dfs為hadoop-hdfs-project\hadoop-hdfs\src\main\java\org\apache\hadoop\hdfs\dfsclient.java。hdfsdatainputstream繼承自fsdatainputstream。構造是并沒有額外的處理。

fsdatainputstream繼承自dfsinputstream。

fetchlocatedblocksandgetlastblocklength通過調用getlocatedblocks實作了示意圖中的步驟二:

你可能會說步驟二調用的是getblocklocations。看以下的代碼:

      然後就可以開始讀檔案的資料了。通過namenode.getblocklocations的遠端調用接口獲得了檔案開始部分的資料塊的儲存位置。對于檔案中的每個塊,nn傳回儲存着該副本的dn的位址。注意,這些dn根據它們與client的距離進行了簡單的排序(利用了網絡的拓撲資訊)。

      client調用hdfsdatainputstream的read方法讀取檔案資料時,dfsinputstream對象會通過和dn間的讀資料stream接口,和最近的dn建立連接配接。client反複調用read方法,資料會通過dn和client的連接配接上的資料包傳回client。當到達塊的末端時,dfsinputstream會關閉和dn的連接配接。并通過getblocklocations()遠端方法獲得儲存着下一個資料塊的dn資訊,嚴格來說,在對象沒有緩存該資料塊的位置時,才會使用這個遠端方法。這就是上圖中的步驟五。然後重複上述過程。

      另外,由于namenode.getblocklocations()不會一次傳回檔案的所有的資料塊資訊,dfsinputstream可能需要多次調用該遠端方法,檢索下一組資料塊的位置資訊。對于使用者來說,它讀取的是一個連續的資料流,上面所講的聯系不同的dn,多次定位資料塊的過程,都是透明的。當使用者完成資料讀取任務後,通過fsdatainputstream.close()關系資料流。即圖中的步驟六。

     如果dn發生了錯誤,如節點停機或者網絡出現故障,那麼client會嘗試連接配接下一個block的位置。同時它會記住出現故障的那個dn,不會再進行徒勞的嘗試。在資料的應答中,不單包含了資料,還包含了資料的校驗和,client會檢查資料的一緻性,如果發現了校驗錯誤,它會将這個資訊報告給nn;同時,嘗試從别的dn讀取另外一個副本的内容。由client在讀取資料時進行資料完整性檢查,可以降低dn的負載,均衡各個節點的計算能力。

     這樣的設計其實可以給我們一個很好的設計大型分布式系統的例子。通過一些有效的設計,将計算和網絡等分散到各個節點上,這樣可以最大程度的保證scalability。

  即使不考慮出現錯誤的情況,寫檔案也是hdfs最複雜的流程。本節通過建立一個新檔案并向檔案寫入資料,結束後關閉這個檔案為例,分析檔案寫入時各個節點之間的配合。

HDFS追本溯源:HDFS操作的邏輯流程與源碼解析1. Client和NN2. Client讀檔案3. Client寫檔案4. DataNode的啟動與心跳機制5. SNN節點的中繼資料合并

      client調用distributedfilesystem.create()建立檔案(上圖中的步驟一),這時distributedfilesystem建立了dfsoutputstream,并由rpc,讓nn執行同名的方法,在檔案系統的命名空間建立一個新檔案。nn建立新檔案時,需要執行檢查,包括nn是否處理正常工作狀态,被建立的檔案不存在,client是否有在父目錄中建立檔案的權限等。通過檢查後,nn會建構一個新檔案,記錄建立操作到編輯日志edits中。rpc結束後,distributedfilesystem将該dfsoutputstream對象包裝到fsdataoutputstream執行個體中,傳回client。

關鍵的調用點有dfsclient.create:

在步驟三client寫入資料時,由于create()調用建立了一個空檔案,是以,dfsoutputstream執行個體首先需要想nn申請資料塊,addblock()方法成功執行後,傳回一個locatedblock對象。該對象包含了新資料塊的資料塊辨別和版本好,同時,它的成員變量locs提供了資料流管道的資訊,通過上述資訊,dfsoutputstream就可以和dn連接配接,通過些資料接口建立資料流管道。client寫入fsdataoutputstream流中的資料,被分成一個一個的檔案包,放入dfsoutputstream對象的内部隊列。該隊列中的檔案包最後打包成資料包,發往資料流管道,流經管道上的各個dn,并持久化,确認包逆流而上,從資料流管道依次發往client,當client收到應答時,它将對應的包從内部隊列删除。

    dfsoutputstream在寫完一個資料塊後,資料流管道上的節點,會通過和nn的datanodeprotocol遠端接口的blockreceived()方法,向nn送出資料塊。如果資料隊列還有等到輸出的資料,dfsoutputstream會再次調用addblock(),為檔案添加新的資料塊。

     client完成資料的寫入後,會調用close()方法關閉流,關閉流意味着client不會再向流中寫入資料。是以,當dfsoutputstream資料隊列的檔案包都收到應答後,就可以使用clientprotocol.complete()方法通知nn關閉檔案,完成一次正常的檔案寫入。

     如果在檔案寫入期間dn發生故障,則會執行下面的操作(注意,這些操作對于寫入資料的client是透明的):

資料流管道會被關閉,已經發送到管道但是還沒有收到确認的檔案包,會被重新添加到dfsoutputstream的輸出隊列,這樣就保證了無路資料流管道的哪個dn發生故障,都不會丢失資料。目前正常工作的dn的資料塊會被賦予一個新的版本号,并通知nn。這樣,失敗的dn在從故障恢複過來以後,上面隻有部分資料的block會因為版本号和nn儲存的版本号不比對而被删除。

在資料流管道中删除錯誤的dn并建立新的管道,繼續寫資料到正常工作的dn。

檔案關閉後,nn會發現該block的副本數沒有達到要求,會選擇一個新的dn并複制block,建立新的副本。dn的故障隻會影響一個block的寫操作,後續block的寫入不會受到影響。

      本節讨論dn的啟動及其與nn之間的互動。包括dn從啟動到進入正常工作狀态的注冊,block上報,以及正常工作過程中的心跳等與nn相關的遠端調用。這部分雖然隻涉及datanodeprotocol的接口,但是有助于進一步了解dn與nn的關系。

      正常啟動的dn或者為更新而啟動的dn,都會向nn發送遠端調用versionrequest(),進行必要的版本檢查。這裡的版本檢查,隻涉及建構版本号,保證它們間的hdfs版本是一緻的。

HDFS追本溯源:HDFS操作的邏輯流程與源碼解析1. Client和NN2. Client讀檔案3. Client寫檔案4. DataNode的啟動與心跳機制5. SNN節點的中繼資料合并

     在版本檢查結束後,dn會接着通過遠端調用register(),向nn注冊。datanodeprotocol.register()的主要工作也是檢查,确認該dn是nn所管理叢集的成員。也就是說,使用者不能把某一個叢集中的某個node直接注冊到另外一個叢集中去,保證了整個系統的資料一緻性。

     注冊成功後,dn會将它所管理的所有block的資訊,通過blockrequest()方法上報到nn(步驟三),以幫助nn建立hdfs檔案資料塊到dn的映射關系。在此後,dn才正式的提供服務。

 由于nn和dn是簡單的主從關系,dn需要每隔一段時間發送心跳到nn(步驟四和步驟五)。如果nn長時間收不到dn的心跳,它會認為dn已經失效。如果nn需要一些dn需要配合的動作,則會通過sendheartbeat()的方法傳回。該傳回值是一個datanodecommand數組,它是nn的指令。

     應該說,dn和nn的互動邏輯非常簡單。大部分是通過dn到nn的心跳來完成的。但是考慮到一定規模的hdfs叢集,一個nn會管理上千個dn,這樣的設計也就非常自然了。

      當client對hdfs的檔案目錄進行修改時,nn都會在edits中留下記錄,以保證在系統出現問題時,通過日志可以進行恢複。

    fsimage是某一個時刻的檢查點(checkpoint)。由于fsimage很大,是以不會在每次的中繼資料修改都寫入到它裡邊,而隻是存在到edits中。在系統啟動時,會首先狀态最近時刻的fsimage,然後在通過edits,恢複系統的最新狀态。

    當時如果edits太大,那麼節點啟動時将用很長的時間來執行日志的每一個操作,使得系統恢複最近的狀态。在啟動恢複的這段時間,服務是不可用的。為了避免edits多大,增加叢集的可用時間,hdfs引入了第二名位元組點,即snn(secondary namenode)。snn不是nn的standby,它隻是輔助nn完成合并(merge)fsimage和edits。過程涉及namenodeprotocol和nn與snn之間的流式接口。

HDFS追本溯源:HDFS操作的邏輯流程與源碼解析1. Client和NN2. Client讀檔案3. Client寫檔案4. DataNode的啟動與心跳機制5. SNN節點的中繼資料合并

    該過程由snn發起,首先通過遠端方法namenodeprotocol.geteditlogsize()獲得nn上edits的大小。如果日志很小,snn就會在指定的時間後重新檢查。否則,繼續通過遠端接口rolleditlog(),啟動一次檢查點的過程。這時,nn需要建立一個新的編輯日志edits.new,後續對中繼資料的改動,都會記錄到這個新日志中。而原有的fsimage和edits,會由snn通過http下載下傳到本地(步驟三和步驟四),在記憶體中進行merge。合并的結果就是fsimage.ckpt。然後snn通過http接口通知nn fsimage已經準備好。nn會通過http get擷取merge好的fsimage。在nn下載下傳完成後,snn會通過namenodeprotocol.rollfsimage(),完成這次檢查點。nn在處理這個遠端方法時,會用fsimage.ckpt 覆寫原來的fsimage,并且将新的edits.new改名為edit。

尊重原創,未經允許不得轉載:http://www.anzhan.me/index.php/archives/233

參考資料:

1. hadoop技術内幕-深入解析hadoop common和hdfs架構設計與實作原理