大數據系列2:Hdfs的讀寫操作

在前文大數據系列1:一文初識Hdfs中,我們對Hdfs有了簡單的認識。

在本文中,我們將會簡單的介紹一下Hdfs文件的讀寫流程,爲後續追蹤讀寫流程的源碼做準備。


Hdfs 架構

首先來個Hdfs的架構圖,圖中中包含了Hdfs 的組成與一些操作。

對於一個客戶端而言,對於Hdfs的操作不外乎也就讀寫兩個操作,接下來就去看看整個流程是怎麼走的。

下面我們由淺及深,分爲簡單流程,詳細流程分別介紹讀寫過程


簡單流程

讀請求流程

客戶端需要讀取數據的時候,流程大致如下:

  1. ClientNameNode發起讀請求
  2. NameNode收到讀請求後,會返回元數據,包括請求文件的數據塊在DataNode的具體位置。
  3. Client根據返回的元數據信息,找到對應的DataNode發起讀請求
  4. DataNode收到讀請求後,會返回對應的Block數據給Client

寫請求流程

客戶端需要寫入數據的時候,流程大致如下:

  1. ClientNameNode發起寫請求,其中包含寫入的文件名,大小等。
  2. NameNode接收到信息,NameNode會將文件的信息存儲到本地,同時判斷客戶端的權限、以及文件是否存在等信息,驗證通過後NameNode返回數據塊可以存儲的DataNode信息。
  3. 客戶端會切割文件爲多個Block,將每個Block寫入DataNode,在DataNode之間通過管道,對Block做數據備份。

詳細流程

讀請求流程

客戶端需要讀取數據的時候,流程大致如下:

  1. 客戶端通過調用FileSystemopen()方法來讀取文件。、
  2. 這個對象是DistributedFileSystem的一個實例,通過遠程調用(RPC)與NameNode溝通,會向NameNode請求需要讀寫文件文件的Block位置信息。
  3. NameNode會進行合法性校驗,然後返回Block位置信息,每一個Block都會返回存有該副本的DataNode地址,並且會根據DtaNode與Client的距離進行排序(這裏的距離是指集羣網絡拓撲的距離,也是儘可能滿足數據本地性的要求)
  4. DistributedFileSystem會返回一個支持文件定位的輸入流FSDataInputStream給客戶端,其中封裝着DFSInputStream對象,該對象管理者DataNodeNameNode之間的I/O
  5. Client對這個輸入流調用read()方法
  6. DFSInputStream存儲了文件中前幾個塊的DataNode地址,然後在文件第一個Block所在的DataNode中連接最近的一個DtaNode。通過對數據流反覆調用read(),可以將數據傳輸到客戶端。
  7. 當到到Block的終點的時候,DFSInputStream會關閉與DataNode的鏈接。然後搜尋下一個BlockDataNode重複6、7步驟。在Client看來,整個過程就是一個連續讀取過程。
  8. 當完成所有Block的讀取後,Client會對FSDataInputStream調用close()

Client 讀取數據流的時候,Block是按照DFSInputStreamDataNode打開新的連接的順序讀取的。
並且在有需要的時候,還會請求NameNode返回下一個批次BlocksDataNode信息

DFSInputStreamDataNode交互的時候出現錯誤,它會嘗試選擇這個Block另一個最近的DataNode,並且標記之前的DataNode避免後續的Block繼續在該DataNode上面出錯。

DFSInputStream也會對來自DataNode數據進行校驗,一旦發現校驗錯誤,也會從其他DataNode讀取該Bclock的副本,並且向NamaNode上報Block錯誤信息。

整個流程下來,我們可以發現Client直接連接到DataNode檢索數據並且通過NameNode知道每個Block的最佳DataNode。

這樣設計有一個好處就是:

因爲數據流量分佈在集羣中的所有DataNode上,所以允許Hdfs擴展到大量併發Client.

與此同時,NamaNode只需要響應Block的位置請求(這些請求存儲在內存中,非常高效),
而不需要提供數據。

否則隨着客戶端數量的快速增加,NameNode會成爲成爲性能的瓶頸。


讀請求流程

客戶端需要寫入數據的時候,流程大致如下:

  1. Client通過create()方法調用DistributedFileSystemcreate()
  2. DistributedFileSystem通過RPCNameNode請求建立在文件系統的明明空間中新建一個文件,此時只是建立了一個空的文件加,並沒有Block
  3. NameNode接收到crete請求後,會進行合法性校驗,比如是否已存在相同文件,Client是否有相關權限。如果校驗通過,NameNode會爲新文件創建一個記錄,並返回一些可用的DataNode。否則客戶端拋出一個IOException
  4. DistributedFileSystem 會返回一個FSDataOutputStream個Client,與讀取數據類似,FSDataOutputStream封裝了一個DFSOutputStream,負責NameNodeDataNode之間交互。
  5. Client調用write()
  6. DFSOutputStream會將數據切分爲一個一個packets,並且將之放入一個內部隊列(data queue),這個隊列會被DataStreamer消費,DataStreamer通過選擇一組合合適DataNodes來寫入副本,並請求NameNode分配新的數據塊。與此同時,DFSOutputStream還維護一個等待DataNode確認的內部包隊列(ack queue)
  7. 這些DataNodes會被組成一個管道(假設備份數量爲3)
  8. 一旦pipeline建立,DataStreamerdata queue中存儲的packet流式傳入管道的第一個DataNode,第一個DataNode存儲Packet並將之轉發到管道中的第二個DataNode,同理,從第二個DataNode轉發到管道中的第三個DataNode
  9. 當所一個packet已經被管道中所有的DataNode確認後,該packet會從ack queue移除。
  10. Client完成數據寫入,調用close(),此操作將所有剩餘的數據包刷新到DataNode管道,等待NameNode返回文件寫入完成的確認信息。
  11. NameNode已經知道文件是由哪個塊組成的(因爲是DataStreamer請求NameNode分配Block的),因此,它只需要等待Block被最小限度地複製,最後返回成功。

如果在寫入的過程中發生了錯誤,會採取以下的操作:

  1. 關閉管道,並將所有在ack queue中的packets加到 data queue的前面,避免故障節點下游的DataNode發生數據丟失。
  2. 給該Block正常DataNode一個新的標記,將之告知NameNode,以便後續故障節點在恢復後能刪除已寫入的部分數據。
  3. 將故障節點從管道中移除,剩下的兩個正常DataNodes重新組成管道,剩餘的數據寫入正常的DataNodes
  4. NameNode發現備份不夠的時候,它會在另一個DataNode上創建一個副本補全,隨後該Blcok將被視爲正常

針對多個DataNode出現故障的情況,我們只要設置 dfs.NameNode.replication.min的副本數(默認爲1),Block將跨集羣異步複製,直到達到其目標複製因子( dfs.replication,默認爲3)爲止.


通俗易懂的理解

上面的讀寫過程可以做一個類比,

NameNode 可以看做是一個倉庫管理員;
DataNode 可以看作是倉庫;

管理員負責管理商品,記錄每個商品所在的倉庫;
倉庫負責存儲商品,同時定期向管理員上報自己倉庫中存儲的商品;
此處的商品可以粗略的理解爲我們的數據。

管理員只能有一個,而倉庫可以有多個。

當我們需要出庫的時候,得先去找管理員,在管理員處取得商品所在倉庫的信息;
我們拿着這個信息到對應倉庫提取我們需要的貨物。

當我們需要入庫的時候,也需要找管理員,覈對權限後告訴我們那些倉庫可以存儲;
我們根據管理員提供的倉庫信息,將商品入庫到對應的倉庫。


存在的問題

上面是關於Hdfs讀寫流程介紹,雖然我分了簡單和詳細,但是實際的讀寫比這個過程複雜得多。

比如如何切塊?
爲何小於塊大小的文件按照實際大小存儲?
備份是如何實現的?
Block的結構等等。

這些內容會在後續的源碼部分詳細解答。

此外,有人也許發現了,前文大數據系列1:一文初識Hdfs中Hdfs架構的介紹和本文讀寫的流程的介紹中,存在一個問題。

就是NameNode的單點故障問題。雖然之前有SecondaryNameNode 輔助NameNode合併fsiamgeedits,但是這個還是無法解決NameNode單點故障的問題。
很多人聽過HA(High Availability) 即高可用,誤以爲高可用就是SecondaryNameNode,其實並不是。

在下一篇文章中會介紹Hdfs高可用的實現方式。

想了解更多內容觀影關注:【兔八哥雜談】

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