在前文大資料系列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
高可用的實現方式。
想了解更多內容觀影關注:【兔八哥雜談】