HDFS進階——文件存儲和讀寫流程

我們在上一篇《分佈式文件系統HDFS——基礎篇》中已經已經對HDFS的基本概念和操作進行了介紹,這一次我們將要更深入的瞭解一下HDFS。你已經知道了HDFS是個分佈式文件系統,那麼你知道上傳到HDFS的文件存在哪裏嗎?文件是怎麼上傳上去的?我們讀取文件的時候,HDFS又是怎麼操作的呢?看完本篇或許你就有一個更清晰的認識了,讓我們開始吧!

特別提示:本文所進行的演示都是在hadoop-2.6.0-cdh5.15.1的版本進行的

一、HDFS上數據的存儲

我們知道一般的磁盤文件系統都是通過硬盤驅動程序直接和物理硬件磁盤打交道,那HDFS也和它們一樣是直接操作物理磁盤嗎?答案是否定的,HDFS和一般的磁盤文件系統不一樣,HDFS的存儲是基於本地磁盤文件系統的。

現在我們來做個小實驗,讓你更清楚的認識HDFS上的文件是怎麼存的,實驗步驟如下(單機HDFS、 Block Size:128M、 副本系數:1):

1.我們上傳一個大小超過128M的文件到HDFS上


#要上傳的文件是JDK的壓縮包,大小186M
[root@hdh100 bin]# ll -h /app/jdk/jdk-8u231-linux-x64.tar.gz 
-rw-r--r--. 1 root root 186M Jun  6 13:18 /app/jdk/jdk-8u231-linux-x64.tar.gz

#上傳到HDFS上的根目錄 ‘/’ 下 
[root@hdh100 bin]# ./hadoop fs -put /app/jdk/jdk-8u231-linux-x64.tar.gz /
20/06/07 15:17:57 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable


上傳後效果如下:

上傳的jdk

從圖中看出,我們可以看出信息如下:

  • a)185.16M 大小的JDK文件被分成了兩個Block;
  • b)兩個Block的大小加起來是整個文件的大小;
  • c)兩個塊都存儲在主機hdh100上

2.找出兩個Block的存儲位置

如果你在安裝HDFS的時候在hdfs-site.xml中配置了hadoop.tmp.dir那麼你的數據就存儲在你配置的目錄下,如果你沒有配置這個選項數據默認是存放在/tmp/hadoop-root下


#跳轉到數據的存放目錄
[root@hdh100 bin]# cd /tmp/hadoop-root/

#根據Block 0的BlockId查找數據塊的位置
[root@hdh100 hadoop-root]# find ./ -name *1073741844*
./dfs/data/current/BP-866925568-192.168.56.100-1591426054281/current/finalized/subdir0/subdir0/blk_1073741844_1020.meta
./dfs/data/current/BP-866925568-192.168.56.100-1591426054281/current/finalized/subdir0/subdir0/blk_1073741844

#根據Block 1的BlockId查找數據塊的位置
[root@hdh100 hadoop-root]# find ./ -name *1073741845*
./dfs/data/current/BP-866925568-192.168.56.100-1591426054281/current/finalized/subdir0/subdir0/blk_1073741845_1021.meta
./dfs/data/current/BP-866925568-192.168.56.100-1591426054281/current/finalized/subdir0/subdir0/blk_1073741845

#跳轉到塊所在的目錄
[root@hdh100 hadoop-root]# cd ./dfs/data/current/BP-866925568-192.168.56.100-1591426054281/current/finalized/subdir0/subdir0/

#查看兩個塊的大小,以'.meta'結尾的文件是元數據
[root@hdh100 subdir0]# ll | grep 107374184[45]
-rw-r--r--. 1 root root 134217728 Jun  7 15:18 blk_1073741844
-rw-r--r--. 1 root root   1048583 Jun  7 15:18 blk_1073741844_1020.meta
-rw-r--r--. 1 root root  59933611 Jun  7 15:18 blk_1073741845
-rw-r--r--. 1 root root    468239 Jun  7 15:18 blk_1073741845_1021.meta


經過這個操作我們得到以下信息:

  • a)HDFS的數據塊存儲在本地文件系統;
  • b)兩個數據塊的大小和web看到的大小是一致的

那麼HDFS是不是就是簡單的把我們上傳的文件給切分一下放置在這裏了,它有做其他操作嗎?我們繼續下一步

3.直接合並兩個文件,驗證我們想法

合併後無非是兩個結果:1.合併後的文件可以直接使用,則說明HDFS對上傳的文件是直接切分,對數據塊無特殊操作;2.合併後文件無法使用,則說明HDFS對拆分後文件進行了特殊處理


#將兩個塊合併
[root@hdh100 subdir0]# cat blk_1073741844 >> /test/jdk.tag.gz
[root@hdh100 subdir0]# cat blk_1073741845 >> /test/jdk.tag.gz

#跳轉到test目錄,test目錄是之前創建的
[root@hdh100 subdir0]# cd /test/

#嘗試解壓文件,發現解壓成功
[root@hdh100 test]# tar -xzvf jdk.tag.gz

經過上面操作,我們僅僅是將兩個塊進行了合併,然後嘗試解壓發現竟然解壓成功了,這也證明了我們猜想HDFS 的文件拆分就是簡單拆分,拆分的塊合併後就直接是原文件。

結論:從上面的實驗我們可以得出以下結論:a)HDFS的文件存儲是基於本地文件系統的,也就是HDFS上的文件被切分後的塊是使用本地文件系統進行存儲;b)上傳文件到HDFS後,HDFS對文件的拆分屬於基本拆分,沒有對拆分後的塊進行特殊處理

二、HDFS寫數據流程

經過上一步我們瞭解了HDFS上的文件存儲,現在我們開始看看HDFS創建一個文件的流程是什麼。

爲了幫助大家理解,錘子畫了一個圖,咱們跟着圖來看

寫流程

結合圖,咱們來描述一下流程

  • 1.客戶端調用DistributedFileSystem的create方法

  • 2.DistributedFileSystem遠程RPC調用NameNode,請求在文件系統的命名空間中創建一個文件

  • 3.NameNode進行相應檢測,包括校驗文件是否已經存在,權限檢驗等,校驗通過則創建一條新文件記錄,否則拋出未校驗通過的相應異常,根據最後處理結果,對最請求端做出響應

  • 4.DistributedFileSystem跟NameNode交互接收到成功信息後,就會根據NameNode給出的信息創建一個FSDataOutputStream。在FSDataOutputStream中持有一個final的DFSOutputStream引用,這個DFSOutputStream負責後續的NameNode和DataNode通信

  • 5.輸出流準備好後,客戶端就開始向輸出流寫數據

  • 6.此時DataStreamer就會向NameNode發出請求創建一個新的Block

  • 7.NameNode接收請求後經過處理,創建一個新的BlockId,並且將該Block應該存儲的位置返回給DataStream

DataStreamer是DFSOutputStream的一個內部類,且DFSOutputStream中還持有一個DataStreamer實例的引用。DataStreamer類負責向管道中的數據節點發送數據包,它會向NameNode申請新的BlockId並檢索Block的位置,並開始將數據包流傳輸到Datanodes的管道。

  • 8.DateStreamer在接收到Block的位置後,會通知需要存儲該Block副本的相應DataNode,讓這一組DataNode形成一個管道。DataStreamer 就開始將數據包流傳輸到Datanodes的管道,每一個DataNode節點接收到數據包流後存儲完成後就轉發給下一個節點,下一個節點存放之後就繼續往下傳遞,直到傳遞到最後一個節點爲止

  • 9.放進DataNodes管道的這些數據包每個都有一個相關的序列號,當一個塊的所有數據包都被髮送出去,並且每個包的ack(存儲成功應答)都被接收到時,DataStreamer就關閉當前塊了。(如果該文件有多個塊,後面就是按照6,7,8,9步驟重複)

  • 10.文件數據全部寫入完畢後,客戶端發出關閉流請求,待NameNode確認所有數據寫入完成後,就把成功信息返回給客戶端

三、HDFS讀數據流程

同樣讀數據流程我們也畫了一個圖

讀流程

我們描述一下流程

  • 1.客戶端調用DistributedFileSystem的open方法

  • 2.DistributedFileSystem遠程RPC調用NameNode,請求讀取指定文件

  • 3.NameNode在接收請求後,查詢該文件的元數據信息,包括文件Block起始塊位置,每個塊副本的DataNode地址,並將DataNode地址按照與當前請求客戶端的距離遠近進行排序,並將信息返回給請求端

  • 4.DistributedFileSystem跟NameNode交互成功接收到該文件的元數據信息後,就會根據NameNode給出的信息創建一個FSDataInputStream。在FSDataInputStream中也持有一個DFSInputStream引用,DFSInputStream負責後續跟DataNode和NameNode通信

  • 5.客戶端對FSDataInputStream發出read請求

  • 6.DFSInputStream連接第一個塊的所有副本中與當前客戶端最近的一個DataNode進行數據讀取,當前塊讀取完成後繼續下一個塊,同樣尋找最佳的DataNode讀取,直到所有塊讀取完成

  • 7.文件所有塊讀取完成後,客戶端關閉流

四、一致模型

文件系統的一致性模型是描述文件讀寫的數據可見性的。HDFS的一致模型中主要有以下特點

  • 新建文件,它在文件系統的命名空間中立即可見

Path path = new Path("/dream-hammer.txt");
fileSystem.create(path);
fileSystem.exists(path) //結果爲true

  • 新建文件的寫入內容不保證立即可見

Path path = new Path("/dream-hammer.txt");
OutputStram out = fileSystem.create(p);
out.write("content");
out.flush();
fileSystem.getFileStatus(path).getLen() //可能爲0,即使數據已經寫入

  • 當前寫入的Block對其他Reader不可見

即當前客戶端正在寫入文件的某個Block,在寫的過程中,其他客戶端是看不到該Block的

hflush()方法:強行刷新緩存數據到DataNode,hflush刷新成功後,HDFS保證文件到目前爲止對所有新的Reader而言都是可見的且寫入數據都已經到達所有DataNode的寫入管道。但是hflush不保證DataNode已經將數據寫到磁盤,僅確保數據在DataNode的內存中。

hsync()方法:也是一種強行刷新緩存數據到DataNode,在hsync()刷新成功後,客戶端所有的數據都發送到每個DataNode上,並且每個DataNode上的每個副本都完成了POSIX中fsync的調用,即操作系統已將數據存儲到磁盤上

調用hflush()和hsync()都會增加HDFS的負載,但是如果不使用強制緩存數據刷新,就會有數據塊丟失的風險,對於大部分應用來說因爲客戶端或者系統系統故障丟失數據是不可接受的,所以實際生產中我們要根據實際場景來適當調用,在數據安全和速度上進行平衡

文章歡迎轉載,轉載請註明出處,個人公衆號【愛做夢的錘子】,全網同id,個站 http://te-amo.site,歡迎關注,裏面會分享更多有用知識,還有我的私密照片

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