HBase原理--RegionServer核心組件之HFile

MemStore中數據落盤以後會造成一個文件寫入HDFS,這個文件稱爲HFile。HFile參考BigTable的SSTable和Hadoop的TFile實現。從HBase誕生到如今,HFile經歷了3個版本,其中V2在0.92引入,V3在0.98引入。HFile V1版本在實際使用過程當中發現佔用內存過多,HFile V2版本針對此問題進行了優化,HFile V3版本和V2版本基本相同,只是在cell層面添加了對Tag數組的支持。鑑於此,本文主要針對V2版本進行分析,對V1和V3版本感興趣的讀者能夠參考社區官方文檔。算法

HFile邏輯結構

HFile V2的邏輯結構如圖所示
image.png數據庫

HFile文件主要分爲4個部分:Scanned block部分、Non-scanned block部分、Load-on-open部分和Trailer。數組

•Scanned Block部分:顧名思義,表示順序掃描HFile時全部的數據塊將會被讀取。這個部分包含3種數據塊:Data Block,Leaf Index Block以及BloomBlock。其中Data Block中存儲用戶的KeyValue數據,Leaf Index Block中存儲索引樹的葉子節點數據,Bloom Block中存儲布隆過濾器相關數據。性能優化

•Non-scanned Block部分:表示在HFile順序掃描的時候數據不會被讀取,主要包括Meta Block和Intermediate Level Data Index Blocks兩部分。數據結構

•Load-on-open部分:這部分數據會在RegionServer打開HFile時直接加載到內存中,包括FileInfo、布隆過濾器MetaBlock、Root Data Index和MetaIndexBlock。架構

•Trailer部分:這部分主要記錄了HFile的版本信息、其餘各個部分的偏移值和尋址信息。app

HFile物理結構

HFile物理結構如圖所示。
image.png
實際上,HFile文件由各類不一樣類型的Block(數據塊)構成,雖然這些Block的類型不一樣,但卻擁有相同的數據結構。異步

Block的大小能夠在建立表列簇的時候經過參數blocksize=> '65535'指定,默認爲64K。一般來說,大號的Block有利於大規模的順序掃描,而小號的Block更有利於隨機查詢。所以用戶在設置blocksize時須要根據業務查詢特徵進行權衡,默認64K是一個相對摺中的大小。函數

HFile中全部Block都擁有相同的數據結構,HBase將全部Block統一抽象爲HFile-Block。HFileBlock支持兩種類型,一種類型含有checksum,另外一種不含有checksum。爲方便講解,本節全部HFileBlock都選用不含有checksum的HFileBlock。HFileBlock結構如圖所示。
image.pngoop

HFileBlock主要包含兩部分:BlockHeader和BlockData。其中BlockHeader主要存儲Block相關元數據,BlockData用來存儲具體數據。Block元數據中最核心的字段是BlockType字段,表示該Block的類型,HBase中定義了8種BlockType,每種BlockType對應的Block都存儲不一樣的內容,有的存儲用戶數據,有的存儲索引數據,有的存儲元數據(meta)。對於任意一種類型的HFileBlock,都擁有相同結構的BlockHeader,可是BlockData結構卻不盡相同。下表羅列了最核心的幾種BlockType。

image.png

HFile的基礎Block

1. Trailer Block

Trailer Block主要記錄了HFile的版本信息、各個部分的偏移值和尋址信息,圖爲Trailer Block的數據結構,其中只顯示了部分核心字段。
image.png

RegionServer在打開HFile時會加載全部HFile的Trailer部分以及load-on-open部分到內存中。實際加載過程會首先會解析Trailer Block,而後再進一步加載load-on-open部分的數據,具體步驟以下:

1)加載HFile version版本信息,HBase中version包含majorVersion和minorVersion兩部分,前者決定了HFile的主版本——V一、V2仍是V3;後者在主版本肯定的基礎上決定是否支持一些微小修正,好比是否支持checksum等。不一樣的版本使用不一樣的文件解析器對HFile進行讀取解析。

2)HBase會根據version信息計算Trailer Block的大小(不一樣version的TrailerBlock大小不一樣),再根據Trailer Block大小加載整個HFileTrailer Block到內存中。Trailer Block中包含不少統計字段,例如,TotalUncompressedBytes表示HFile中全部未壓縮的KeyValue總大小。NumEntries表示HFile中全部KeyValue總數目。Block中字段CompressionCodec表示該HFile所使用的壓縮算法,HBase中壓縮算法主要有lzo、gz、snappy、lz4等,默認爲none,表示不使用壓縮。

3)Trailer Block中另兩個重要的字段是LoadOnOpenDataOffset和LoadOnOpenDataSize,前者表示load-on-open Section在整個HFile文件中的偏移量,後者表示load-on-open Section的大小。根據此偏移量以及大小,HBase會在啓動後將load-on-open Section的數據所有加載到內存中。load-on-open部分主要包括FileInfo模塊、Root Data Index模塊以及布隆過濾器Metadata模塊,FileInfo是固定長度的數據塊,主要記錄了文件的一些統計元信息,比較重要的是AVG_KEY_LEN和AVG_VALUE_LEN,分別記錄了該文件中全部Key和Value的平均長度。Root Data Index表示該文件數據索引的根節點信息,布隆過濾器Metadata記錄了HFile中布隆過濾器的相關元數據。

2. Data Block
Data Block是HBase中文件讀取的最小單元。Data Block中主要存儲用戶的KeyValue數據,而KeyValue結構是HBase存儲的核心。HBase中全部數據都是以KeyValue結構存儲在HBase中。

內存和磁盤中的Data Block結構如圖所示。
image.png

KeyValue由4個部分構成,分別爲Key Length、Value Length、Key和Value。其中,Key Length和Value Length是兩個固定長度的數值,Value是用戶寫入的實際數據,Key是一個複合結構,由多個部分構成:Rowkey、Column Family、Column Qualif ier、TimeStamp以及KeyType。其中,KeyType有四種類型,分別是Put、Delete、DeleteColumn和DeleteFamily。

由Data Block的結構能夠看出,HBase中數據在最底層是以KeyValue的形式存儲的,其中Key是一個比較複雜的複合結構,這點最先在第1章介紹HBase數據模型時就提到過。由於任意KeyValue中都包含Rowkey、Column Family以及ColumnQualif ier,所以這種存儲方式實際上比直接存儲Value佔用更多的存儲空間。這也是HBase系統在表結構設計時常常強調Rowkey、Column Family以及ColumnQualif ier儘量設置短的根本緣由。

HFile中與布隆過濾器相關的Block

布隆過濾器對HBase的數據讀取性能優化相當重要。HBase是基於LSM樹結構構建的數據庫系統,數據首先寫入內存,而後異步f lush到磁盤造成文件。這種架構自然對寫入友好,而對數據讀取並不十分友好,由於隨着用戶數據的不斷寫入,系統會生成大量文件,用戶根據Key獲取對應的Value,理論上須要遍歷全部文件,在文件中查找指定的Key,這無疑是很低效的作法。使用布隆過濾器能夠對數據讀取進行相應優化,對於給定的Key,通過布隆過濾器處理就能夠知道該HFile中是否存在待檢索Key,若是不存在就不須要遍歷查找該文件,這樣就能夠減小實際IO次數,提升隨機讀性能。布隆過濾器一般會存儲在內存中,因此布隆過濾器處理的整個過程耗時基本能夠忽略。

HBase會爲每一個HFile分配對應的位數組,KeyValue在寫入HFile時會先對Key通過多個hash函數的映射,映射後將對應的數組位置爲1,get請求進來以後再使用相同的hash函數對待查詢Key進行映射,若是在對應數組位上存在0,說明該get請求查詢的Key確定不在該HFile中。固然,若是映射後對應數組位上所有爲1,則表示該文件中有可能包含待查詢Key,也有可能不包含,須要進一步查找確認。

能夠想象,HFile文件越大,裏面存儲的KeyValue值越多,位數組就會相應越大。一旦位數組太大就不適合直接加載到內存了,所以HFile V2在設計上將位數組進行了拆分,拆成了多個獨立的位數組(根據Key進行拆分,一部分連續的Key使用一個位數組)。這樣,一個HFile中就會包含多個位數組,根據Key進行查詢時,首先會定位到具體的位數組,只須要加載此位數組到內存進行過濾便可,從而下降了內存開銷。

在文件結構上每一個位數組對應HFile中一個Bloom Block,所以多個位數組實際上會對應多個Bloom Block。爲了方便根據Key定位對應的位數組,HFile V2又設計了相應的索引Bloom Index Block,對應的內存和邏輯結構如圖所示。

image.png
Bloom Index Block結構

整個HFile中僅有一個Bloom Index Block數據塊,位於load-on-open部分。Bloom Index Block從大的方面看由兩部份內容構成,其一是HFile中布隆過濾器的元數據基本信息,其二是構建了指向Bloom Block的索引信息。

Bloom Index Block結構中TotalByteSize表示位數組大小,NumChunks表示Bloom Block的個數,HashCount表示hash函數的個數,HashType表示hash函數的類型,TotalKeyCount表示布隆過濾器當前已經包含的Key的數目,TotalMaxKeys表示布隆過濾器當前最多包含的Key的數目。

Bloom Index Entry對應每個Bloom Block的索引項,做爲索引分別指向scanned block部分的Bloom Block,Bloom Block中實際存儲了對應的位數組。Bloom Index Entry的結構見圖5-13中間部分,其中BlockKey是一個很是關鍵的字段,表示該Index Entry指向的Bloom Block中第一個執行Hash映射的Key。BlockOffset表示對應Bloom Block在HFile中的偏移量。

所以,一次get請求根據布隆過濾器進行過濾查找須要執行如下三步操做:

1)首先根據待查找Key在Bloom Index Block全部的索引項中根據BlockKey進行二分查找,定位到對應的Bloom Index Entry。

2)再根據Bloom Index Entry中BlockOffset以及BlockOndiskSize加載該Key對應的位數組。

3)對Key進行Hash映射,根據映射的結果在位數組中查看是否全部位都爲1,若是不是,表示該文件中確定不存在該Key,不然有可能存在。

HFile中索引相關的Block

根據索引層級的不一樣,HFile中索引結構分爲兩種:single-level和multi-level,前者表示單層索引,後者表示多級索引,通常爲兩級或三級。HFile V1版本中只有single-level一種索引結構,V2版本中引入多級索引。之因此引入多級索引,是由於隨着HFile文件愈來愈大,Data Block愈來愈多,索引數據也愈來愈大,已經沒法所有加載到內存中,多級索引能夠只加載部分索引,從而下降內存使用空間。同布隆過濾器內存使用問題同樣,這也是V1版本升級到V2版本最重要的因素之一。

V2版本Index Block有兩類:Root Index Block和NonRoot Index Block。NonRoot Index Block又分爲Intermediate Index Block和Leaf Index Block兩種。HFile中索引是樹狀結構,Root Index Block表示索引數根節點,Intermediate Index Block表示中間節點,Leaf Index Block表示葉子節點,葉子節點直接指向實際Data Block,如圖所示。

image.png
HFile文件索引

須要注意的是,這三種Index Block在HFile中位於不一樣的部分,Root Index Block位於「 load-on-open」部分,會在RegionServer打開HFile時加載到內存中。Intermediate Index Block位於「Non-Scanned block」部分,Leaf Index Block位於「scanned block」部分。

HFile中除了Data Block須要索引以外,Bloom Block也須要索引,Bloom索引結構實際上採用了單層結構,Bloom Index Block就是一種Root Index Block。

對於Data Block,因爲HFile剛開始數據量較小,索引採用單層結構,只有RootIndex一層索引,直接指向Data Block。當數據量慢慢變大,Root Index Block大小超過閾值以後,索引就會分裂爲多級結構,由一層索引變爲兩層,根節點指向葉子節點,葉子節點指向實際Data Block。若是數據量再變大,索引層級就會變爲三層。

下面針對Root index Block和NonRoot index Block兩種結構進行解析(Intermediate Index Block和Ieaf Index Block在內存和磁盤中存儲格式相同,都爲NonRoot Index Block格式)。

1. Root Index Block
Root Index Block表示索引樹根節點索引塊,既能夠做爲Bloom Block的直接索引,也能夠做爲Data Block多極索引樹的根索引。對於單層和多級這兩種索引結構,對應的Root Index Block結構略有不一樣,單層索引結構是多級索引結構的一種簡化場景。本書以多級索引結構中的Root Index Block爲例進行分析,圖爲Root Index Block的結構圖。
image.png

圖中,Index Entry表示具體的索引對象,每一個索引對象由3個字段組成:Block Offset表示索引指向Data Block的偏移量,BlockDataSize表示索引指向Data Block在磁盤上的大小,BlockKey表示索引指向Data Block中的第一個Key。

除此以外,還有另外3個字段用來記錄MidKey的相關信息,這些信息用於在對HFile進行split操做時,快速定位HFile的切分點位置。須要注意的是單層索引結構和多級索引結構相比,僅缺乏與MidKey相關的這三個字段。

Root Index Block位於整個HFile的「 load-on-open 」部分,所以會在RegionServer打開HFile時直接加載到內存中。此處須要注意的是,在Trailer Block中有一個字段爲DataIndexCount,表示Root Index Block中Index Entry的個數,只有知道Entry的個數才能正確地將全部Index Entry加載到內存。

2. NonRoot Index Block

當HFile中Data Block愈來愈多,單層結構的根索引會不斷膨脹,超過必定閾值以後就會分裂爲多級結構的索引結構。多級結構中根節點是Root Index Block。而索引樹的中間層節點和葉子節點在HBase中存儲爲NonRoot Index Block,但從Block結構的視角分析,不管是中間節點仍是葉子節點,其都擁有相同的結構,如圖所示。

image.png

和Root Index Block相同,NonRoot Index Block中最核心的字段也是IndexEntry,用於指向葉子節點塊或者Data Block。不一樣的是,NonRoot Index Block結構中增長了Index Entry的內部索引Entry Offset字段,Entry Offset表示IndexEntry在該Block中的相對偏移量(相對於第一個Index Entry),用於實現Block內的二分查找。經過這種機制,全部非根節點索引塊(包括Intermediate Index Block和Leaf Index Block)在其內部定位一個Key的具體索引並非經過遍歷實現,而是使用二分查找算法,這樣能夠更加高效快速地定位到待查找Key。

文章基於《HBase原理與實踐》一書

相關文章
相關標籤/搜索