HBase篇(4)-你不知道的HFile
【每日五分鐘搞定大數據】系列,HBase第四篇
這一篇你可以知道,
HFile的內部結構?
HBase讀文件細粒度的過程?
HBase隨機讀寫快除了MemStore之外的原因?
上一篇中提到了Hbase的數據以HFile的形式存在HDFS, 物理存儲路徑是:
NameSpace->Table->Region->CF->HFile
這一篇我們來說下這個HFile,把路徑從HFile開始再補充一下
HFile->Block->KeyValue.
順便科普一下,HFile具體存儲路徑為:
/hbase/data/<nameSpace>/<tableName>/<encoded-regionname>/<column-family>/<filename>
如何讀取HFile的內容:
hbase org.apache.hadoop.hbase.io.hfile.HFile -f /上面的路徑指定某個HFile -p
這是我做的一個思維導圖,這裏面的內容就是我這章要講的東西,有點多,大家慢慢消化。
HFile的邏輯分類
Scanned block section:掃描HFile時這個部分裏面的所有block都會被讀取到。
Non-scanned block section:和相面的相反,掃描HFile時不會被讀取到。
Load-on-open-section:regionServer啟動時就會加載這個部分的數據,不過不是最先加載。
Trailer:這個部分才是最先加載到內存的,記錄了各種偏移量和版本信息。
HFile的物理分類
物理分類和邏輯分類對應的關系,可以在上面的圖中看到
HFile有多個大小相等的block組成,Block分為四種類型:Data Block,Index Block,Bloom Block和Meta Block。
- Data Block
用於存儲實際數據,通常情況下每個Data Block可以存放多條KeyValue數據對; - Index Block和Bloom Block
都用於優化隨機讀的查找路徑,其中Index Block通過存儲索引數據加快數據查找,而Bloom Block通過一定算法可以過濾掉部分一定不存在待查KeyValue的數據文件,減少不必要的IO操作; - Meta Block
主要存儲整個HFile的元數據。
Data Block
保存了實際的數據,由多個KeyValue 組成,塊大小默認為64K(由建表時創建cf時指定或者HColumnDescriptor.setBlockSize(size)),在查詢數據時,以block為單位加載數據到內存。
KeyValue 的結構
- key
由這些內容組成:rowkey長度、rowkeyColumnFamily的長度、ColumnFamily、ColumnQualifier、KeyType(put、Delete、 DeleteColumn和DeleteFamily) - key length
固定長度的數值 - value
二進制數據 - value length
固定長度的數值
Index Block
- data block index(Root Index Block )
Data Block第一層索引 - Intermediate Level Data Index Block
Data Block第二層索引 - Leaf Index Block
Data Block第三層索引
這三層索引我舉個栗子放在一起說,第一層是必須要的,也是最快的,因為它會被加載到內存中。二三根據數據量決定,如果有的話在找的時候也會加載到內存。實際上就是一步步的縮小範圍,類似B+樹的結構:
a,b,c,d,e
f,g,h,i,j
k,l,m,n,o
Root Index Block 第一層:a,g,l
Intermediate Level Data Index Block 第二層:a,c,e || f,h,j || k ,m,o
Leaf Index Block 第三層(部分):a,b || c,d || e,f
- 假設要搜索的rowkey為bb,root index block(常駐內存)中有三個索引a,g,l,b在a和g之間,因此會去找索引 a 指向的二層索引
- 將索引 a 指向的中間節點索引塊加載到內存,然後通過二分查找定位到 b 在 index a 和 c 之間,接下來訪問索引 a 指向的葉子節點。
- 將索引 a 指向的中間節點索引塊加載到內存,通過二分查找定位找到 b 在 index a 和 b 之間,最後需要訪問索引b指向的數據塊節點。
- 將索引 b 指向的數據塊加載到內存,通過遍歷的方式找到對應的 keyvalue 。
上面的流程一共IO了三次,HBase提供了一個BlockCache,是用在第4步緩存數據塊,可以有一定概率免去隨後一次IO。
相關配置:
hfile.data.block.size(默認64K):同樣的數據量,數據塊越小,數據塊越多,索引塊相應的也就越多,索引層級就越深
hfile.index.block.max.size(默認128K):控制索引塊的大小,索引塊越小,需要的索引塊越多,索引的層級越深
Meta Block (可選的)
保存用戶自定義的kv對,可以被壓縮。比如booleam filter就是存在元數據塊中的,該塊只保留value值,key值保存在元數據索引塊中。每一個元數據塊由塊頭和value值組成。可以快速判斷key是都在這個HFile中。
meta block index (可選的)
Meta Block的索引。
File Info ,Hfile的元信息
不被壓縮,用戶也可以在這一部分添加自己的元信息。
Trailer (記錄起始位置)
記錄了HFile的基本信息、偏移值和尋址信息
Trailer Block
- version
最先加載到內存的部分,根據version確定Trailer長度,再加載整個Trailer block - LoadOnOpenDataOffset
load-on-open區的偏移量(便於將其加載到內存) - FirstDataBlockOffset:HFile中第一個Block的偏移量
- LastDataBlockOffset:HFile中最後一個Block的偏移量
- numEntries:HFile中kv總數
另外:Bloom filter相關的Block我準備專門寫一篇文章,因為Bloom filter這個東西在分布式系統中非常常見而且有用,現在需要知道的是它是用來快速判斷你需要查找的rowKey是否存在於HFile中(一堆的rowKey中)。
重點來了!
看完上面的內容我們就可以解決文章開始提出的問題了:
HBase讀文件細粒度的過程?
HBase隨機讀寫快除了MemStore之外的原因?
這兩個問題我一起回答。
0.這裏從找到對應的Region開始說起,前面的過程可以看上一篇文章。
1.首先用MemStoreScanner搜索MemStore裏是否有所查的rowKey(這一步在內存中,很快),
2.同時也會用Bloom Block通過一定算法過濾掉大部分一定不包含所查rowKey的HFile,
3.上面提到在RegionServer啟動的時候就會把Trailer,和Load-on-open-section裏的block先後加載到內存,
所以接下來會查Trailer,因為它記錄了每個HFile的偏移量,可以快速排除掉剩下的部分HFile。
4.經過上面兩步,剩下的就是很少一部分的HFile了,就需要根據Index Block索引數據(這部分的Block已經在內存)快速查找rowkey所在的block的位置;
5.找到block的位置後,檢查這個block是否在blockCache中,在則直接去取,如果不在的話把這個block加載到blockCache進行緩存,
當下一次再定位到這個Block的時候就不需要再進行一次IO將整個block讀取到內存中。
6.最後掃描這些讀到內存中的Block(可能有多個,因為有多版本),找到對應rowKey返回需要的版本。
另外,關於blockCache很多人都理解錯了,這裏要註意的是:
blockCache並沒有省去掃描定位block這一步,只是省去了最後將Block加載到內存的這一步而已。
這裏又引出一個問題,如果BlockCache中有需要查找的rowKey,但是版本不是最新的,那會不會讀到臟數據?
HBase是多版本共存的,有多個版本的rowKey那說明這個rowKey會存在多個Block中,其中一個已經在BlockCache中,則省去了一次IO,但是其他Block的IO是無法省去的,它們也需要加載到BlockCache,然後多版本合並,獲得需要的版本返回。解決多版本的問題,也是rowKey需要先定位Block然後才去讀BlockCache的原因。
HBase篇(4)-你不知道的HFile