深入理解 HDFS(一):Block

HDFS 使用類似 Linux 文件目錄結(jié)構(gòu)來抽象表示存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu),使用 inode 來表示目錄或文件。
當(dāng)你通過 hdfs dfs -copyFromLocal my.log /
命令上傳一份日志文件,還是使用計(jì)算引擎存儲(chǔ)臨時(shí)狀態(tài)或計(jì)算結(jié)果,你看見 HDFS 目錄上的所有文件,在實(shí)際存儲(chǔ)時(shí)都會(huì)被切割加工成一個(gè)個(gè)的塊(默認(rèn)大小 128M,快還有副本默認(rèn) 3 個(gè)),最后這些 block 的元數(shù)據(jù)信息會(huì)統(tǒng)一存儲(chǔ)在 NameNode 中。
1. 什么是 Block
上面所說的塊就是我們今天的主角 Block:HDFS 存儲(chǔ)數(shù)據(jù)的基本單元,其核心屬性有:
id:唯一標(biāo)識(shí),long 類型,默認(rèn)情況下從 1073741824(2 的 30 次方)開始,每創(chuàng)建 1 個(gè) block 自增 1;
blockName:塊的名稱,字符串 blk_ + id,例如 blk_1073741825、blk_1073741826 等;
numBytes:塊的大小,long 類型,通常情況下 <= 128M,小于的原因要么文件本身大小 < 128M 或者是文件對(duì)應(yīng) blocks 的最后一塊;
generationStamp:郵戳信息,long 類型,NameNode 中 GenerationStamp 類負(fù)責(zé)生成默認(rèn)從 1000 開始,每創(chuàng)建 1 個(gè) Block 自增 1,用于 DataNode 重新加入集群時(shí)檢測(cè)過時(shí)的副本;
bcId:塊集合 id 即 文件 inodeId,long 類型,NameNode 中 INodeId 負(fù)責(zé)生成默認(rèn)從 16384(2 的 14 次方)開始,根目錄
/
使用 16385,之后每創(chuàng)建目錄或文件自增 1。
2. 從文件到 Block
有的文件只有 1 個(gè) block,有的卻有多個(gè),為什么會(huì)有多個(gè)??jī)?nèi)部切割邏輯是什么?
我們假設(shè)變量 blockSize 表示 block 默認(rèn)大小,文件如何切割會(huì)有以下 2 種情景:
情景 1:文件大小 <= blockSize

對(duì)于大小 <= blockSize 的文件只有 1 個(gè) block。
情景 2:文件大小 > blockSize

對(duì)于大小 > blockSize 的文件至少有 2 個(gè) block,block 數(shù)量計(jì)算可以理解為:Double.valueOf(Math.ceil( 文件大小 * 1.0 / blockSize)).longValue()
3. Block 物理存儲(chǔ)
3.1 準(zhǔn)備工作
為了調(diào)研做了一些前期準(zhǔn)備工作:
搭建 hadoop 偽分布式集群;
查看 hadoop 集群狀態(tài);
上傳文件到 HDFS;
查看上傳文件元數(shù)據(jù)信息。
參照 HDFS 官網(wǎng)在本地搭建了偽分布式集群,可使用命令 hdfs dfsadmin -report
查看集群狀態(tài):

上傳本地 697M 的 hadoop 安裝文件到 HDFS,通過 hdfs fsck
查看文件被切割成 6 個(gè) block:
# 創(chuàng)建 /tmp 目錄
hdfs dfs -mkdir /tmp
# 上傳文件
hdfs dfs -copyFromLocal hadoop-3.3.6.tar.gz /tmp
# 查看是否上傳成功
hdfs dfs -ls -h /tmp
# 查看實(shí)際的 Block
hdfs fsck /tmp/hadoop-3.3.6.tar.gz -files -locations -blocks

3.2 物理存儲(chǔ)位置
那 HDFS 實(shí)際落盤文件存儲(chǔ)在什么地方?
find /tmp -iname "blk_1073741825"
# 輸出如下:
#/tmp/hadoop-root/dfs/data/current/BP-473427094-127.0.0.1-1690621293256/current/finalized/subdir0/subdir0/blk_1073741825
cd /tmp/hadoop-root/dfs/data/current/BP-473427094-127.0.0.1-1690621293256/current/finalized/subdir0/subdir0
ls -lh

從上圖可以看出前 5 個(gè) block 大小都是 128M,最后一個(gè) block 是 57 M:697 - 128 * 5(MB)和預(yù)想的一致,此時(shí)還發(fā)現(xiàn)每個(gè) block 都會(huì)對(duì)應(yīng) 1 個(gè)數(shù)據(jù)文件和 1 個(gè)元數(shù)據(jù)文件,文件命名方式:
block 數(shù)據(jù)文件命名:blk_ + blockId
block 元數(shù)據(jù)文件命名:blk_ + blockId + _ + generationStamp + .meta
3.3 Block 數(shù)據(jù)文件驗(yàn)證
那 block 對(duì)應(yīng)的數(shù)據(jù)文件僅僅是按 128M 大小進(jìn)行切割嗎?HDFS 有沒有做其它的處理?
為了一探究竟,首先使用 split 命令按照 128M 大小切割原始的 hadoop 安裝文件:
# 按照大小 128M 切割文件,文件切割后:
split -b 128M hadoop-3.3.6.tar.gz
# 對(duì)切割后的文件計(jì)算 md5
ls | grep xa | grep -v 'grep' | xargs md5sum
原始文件最終切割成 xaa...xaf 6 個(gè)文件,之后計(jì)算每個(gè)文件的 md5。

現(xiàn)在讓我們計(jì)算 6 個(gè) block 數(shù)據(jù)文件的 md5:
ls -lh
ls | grep blk | grep -Ev 'grep|*.meta' | xargs md5sum
復(fù)制代碼

將上面的兩種方式輸出的結(jié)果進(jìn)行對(duì)比,6 個(gè)文件的 md5 分別相等,那么可以說明:HDFS 僅僅是按照 128M blockSize 的大小做了切割。
3.4 有趣的實(shí)驗(yàn):128M + 1 個(gè)字節(jié)的文件有幾個(gè) Block ?
如果上傳 1 個(gè) 128M + 1 個(gè)字節(jié)的文件后,生成幾個(gè) block ?
# 生成 128M + 1 字節(jié)的文件,文件大?。?28 * 1024 * 1024 + 1 = 134217729
fio -filename=/root/128m1b.txt -direct=1 -ioengine=libaio -bs=4k -size=134217729 -numjobs=1 -iodepth=16 -runtime=1 -thread -rw=write -group_reporting -name="write_test"
# 上傳到 hdfs?
hdfs dfs -copyFromLocal 128m1b.txt /tmp
# 查看究竟是幾個(gè) block
hdfs fsck /tmp/128m1b.txt -files -locations -blocks

看來 HDFS 切割 block 還是很認(rèn)真的。
4. 下載 NameNode 元數(shù)據(jù)信息
NameNode 存儲(chǔ)元數(shù)據(jù)信息的文件前綴為 fsimage_,下面定位下目錄:
# 定位目錄
find /tmp -iname "fsimage_*"
# 定位個(gè)最大編號(hào)的?
fsimage
ls -alht /tmp/hadoop-root/dfs/name/current/
# 進(jìn)入 home 目錄,不要對(duì) hadoop 工作目錄造成影響
cd ~
# 導(dǎo)出元數(shù)據(jù)信息到 xml 文件中
hdfs oiv -i /tmp/hadoop-root/dfs/name/current/fsimage_0000000000000000078 -o fsimage.xml -p XML
# 打開 fsimage.xml 文件
vim fsimage.xml ?# xml格式化 :%!xmllint --format %

打開 fsimage.xml 同樣可以定位到 block 信息:

最后,大家如果有問題,歡迎留言討論!