在前文大數據系列1:一文初識Hdfs中,我們對Hdfs
有了簡單的認識。
在本文中,我們將會簡單的介紹一下Hdfs
文件的讀寫流程,為后續追蹤讀寫流程的源碼做准備。
Hdfs 架構
首先來個Hdfs
的架構圖,圖中中包含了Hdfs
的組成與一些操作。
對於一個客戶端而言,對於Hdfs的操作不外乎也就讀寫兩個操作,接下來就去看看整個流程是怎么走的。
下面我們由淺及深,分為簡單流程,詳細流程分別介紹讀寫過程
簡單流程
讀請求流程
客戶端需要讀取數據的時候,流程大致如下:
Client
向NameNod
e發起讀請求NameNode
收到讀請求后,會返回元數據,包括請求文件的數據塊在DataNode
的具體位置。Client
根據返回的元數據信息,找到對應的DataNode
發起讀請求DataNode
收到讀請求后,會返回對應的Block
數據給Client
。
寫請求流程
客戶端需要寫入數據的時候,流程大致如下:
Client
向NameNode
發起寫請求,其中包含寫入的文件名,大小等。NameNode
接收到信息,NameNode
會將文件的信息存儲到本地,同時判斷客戶端的權限、以及文件是否存在等信息,驗證通過后NameNode
返回數據塊可以存儲的DataNode
信息。- 客戶端會切割文件為多個
Block
,將每個Block
寫入DataNode
,在DataNode
之間通過管道,對Block
做數據備份。
詳細流程
讀請求流程
客戶端需要讀取數據的時候,流程大致如下:
- 客戶端通過調用
FileSystem
的open()
方法來讀取文件。、 - 這個對象是
DistributedFileSystem
的一個實例,通過遠程調用(RPC)與NameNode
溝通,會向NameNode
請求需要讀寫文件文件的Block
位置信息。 NameNode
會進行合法性校驗,然后返回Block
位置信息,每一個Block
都會返回存有該副本的DataNode
地址,並且會根據DtaNode與Client的距離進行排序(這里的距離是指集群網絡拓撲的距離,也是盡可能滿足數據本地性的要求)DistributedFileSystem
會返回一個支持文件定位的輸入流FSDataInputStream
給客戶端,其中封裝着DFSInputStream
對象,該對象管理者DataNode
和NameNode
之間的I/O
Client
對這個輸入流調用read()
方法DFSInputStream
存儲了文件中前幾個塊的DataNode
地址,然后在文件第一個Block
所在的DataNode
中連接最近的一個DtaNode
。通過對數據流反復調用read()
,可以將數據傳輸到客戶端。- 當到到
Block
的終點的時候,DFSInputStream
會關閉與DataNode
的鏈接。然后搜尋下一個Block
的DataNode
重復6、7步驟。在Client
看來,整個過程就是一個連續讀取過程。 - 當完成所有
Block
的讀取后,Client
會對FSDataInputStream
調用close()
Client
讀取數據流的時候,Block
是按照DFSInputStream
與DataNode
打開新的連接的順序讀取的。
並且在有需要的時候,還會請求NameNode
返回下一個批次Blocks
的DataNode
信息
在DFSInputStream
與DataNode
交互的時候出現錯誤,它會嘗試選擇這個Block
另一個最近的DataNode
,並且標記之前的DataNode
避免后續的Block
繼續在該DataNode
上面出錯。
DFSInputStream
也會對來自DataNode
數據進行校驗,一旦發現校驗錯誤,也會從其他DataNode
讀取該Bclock
的副本,並且向NamaNode
上報Block
錯誤信息。
整個流程下來,我們可以發現Client直接連接到DataNode檢索數據並且通過NameNode知道每個Block的最佳DataNode。
這樣設計有一個好處就是:
因為數據流量分布在集群中的所有DataNode
上,所以允許Hdfs擴展到大量並發Client.
與此同時,NamaNode
只需要響應Block
的位置請求(這些請求存儲在內存中,非常高效),
而不需要提供數據。
否則隨着客戶端數量的快速增加,NameNode會成為成為性能的瓶頸。
讀請求流程
客戶端需要寫入數據的時候,流程大致如下:
Client
通過create()
方法調用DistributedFileSystem
的create()
DistributedFileSystem
通過RPC
向NameNode
請求建立在文件系統的明明空間中新建一個文件,此時只是建立了一個空的文件加,並沒有Block
。NameNode
接收到crete
請求后,會進行合法性校驗,比如是否已存在相同文件,Client
是否有相關權限。如果校驗通過,NameNode
會為新文件創建一個記錄,並返回一些可用的DataNode
。否則客戶端拋出一個IOException
DistributedFileSystem
會返回一個FSDataOutputStream個Client
,與讀取數據類似,FSDataOutputStream
封裝了一個DFSOutputStream
,負責NameNode
與DataNode
之間交互。Client
調用write()
DFSOutputStream
會將數據切分為一個一個packets
,並且將之放入一個內部隊列(data queue
),這個隊列會被DataStreamer
消費,DataStreamer
通過選擇一組合合適DataNodes
來寫入副本,並請求NameNode
分配新的數據塊。與此同時,DFSOutputStream
還維護一個等待DataNode
確認的內部包隊列(ack queue
)- 這些
DataNodes
會被組成一個管道(假設備份數量為3
) - 一旦
pipeline
建立,DataStreamer
將data queue
中存儲的packet
流式傳入管道的第一個DataNode
,第一個DataNode存儲Packet並將之轉發到管道中的第二個DataNode
,同理,從第二個DataNode
轉發到管道中的第三個DataNode
。 - 當所一個
packet
已經被管道中所有的DataNode
確認后,該packet
會從ack queue
移除。 - 當
Client
完成數據寫入,調用close()
,此操作將所有剩余的數據包刷新到DataNode
管道,等待NameNode
返回文件寫入完成的確認信息。 NameNode
已經知道文件是由哪個塊組成的(因為是DataStreamer
請求NameNode
分配Block
的),因此,它只需要等待Block
被最小限度地復制,最后返回成功。
如果在寫入的過程中發生了錯誤,會采取以下的操作:
- 關閉管道,並將所有在
ack queue
中的packets
加到data queue
的前面,避免故障節點下游的DataNode
發生數據丟失。 - 給該
Block
正常DataNode
一個新的標記,將之告知NameNode
,以便后續故障節點在恢復后能刪除已寫入的部分數據。 - 將故障節點從管道中移除,剩下的兩個正常
DataNodes
重新組成管道,剩余的數據寫入正常的DataNodes
。 - 當
NameNode
發現備份不夠的時候,它會在另一個DataNode
上創建一個副本補全,隨后該Blcok
將被視為正常
針對多個DataNode
出現故障的情況,我們只要設置 dfs.NameNode.replication.min
的副本數(默認為1),Block
將跨集群異步復制,直到達到其目標復制因子( dfs.replication
,默認為3)為止.
通俗易懂的理解
上面的讀寫過程可以做一個類比,
NameNode
可以看做是一個倉庫管理員;
DataNode
可以看作是倉庫;
管理員負責管理商品,記錄每個商品所在的倉庫;
倉庫負責存儲商品,同時定期向管理員上報自己倉庫中存儲的商品;
此處的商品可以粗略的理解為我們的數據。
管理員只能有一個,而倉庫可以有多個。
當我們需要出庫的時候,得先去找管理員,在管理員處取得商品所在倉庫的信息;
我們拿着這個信息到對應倉庫提取我們需要的貨物。
當我們需要入庫的時候,也需要找管理員,核對權限后告訴我們那些倉庫可以存儲;
我們根據管理員提供的倉庫信息,將商品入庫到對應的倉庫。
存在的問題
上面是關於Hdfs
讀寫流程介紹,雖然我分了簡單和詳細,但是實際的讀寫比這個過程復雜得多。
比如如何切塊?
為何小於塊大小的文件按照實際大小存儲?
備份是如何實現的?
Block
的結構等等。
這些內容會在后續的源碼部分詳細解答。
此外,有人也許發現了,前文大數據系列1:一文初識Hdfs中Hdfs架構的介紹和本文讀寫的流程的介紹中,存在一個問題。
就是NameNode
的單點故障問題。雖然之前有SecondaryNameNode
輔助NameNode
合並fsiamge
和edits
,但是這個還是無法解決NameNode
單點故障的問題。
很多人聽過HA(High Availability)
即高可用,誤以為高可用就是SecondaryNameNode
,其實並不是。
在下一篇文章中會介紹Hdfs
高可用的實現方式。
想了解更多內容觀影關注:【兔八哥雜談】