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 V2的邏輯結構如圖所示
數據庫
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文件由各類不一樣類型的Block(數據塊)構成,雖然這些Block的類型不一樣,但卻擁有相同的數據結構。異步
Block的大小能夠在建立表列簇的時候經過參數blocksize=> '65535'指定,默認爲64K。一般來說,大號的Block有利於大規模的順序掃描,而小號的Block更有利於隨機查詢。所以用戶在設置blocksize時須要根據業務查詢特徵進行權衡,默認64K是一個相對摺中的大小。函數
HFile中全部Block都擁有相同的數據結構,HBase將全部Block統一抽象爲HFile-Block。HFileBlock支持兩種類型,一種類型含有checksum,另外一種不含有checksum。爲方便講解,本節全部HFileBlock都選用不含有checksum的HFileBlock。HFileBlock結構如圖所示。
oop
HFileBlock主要包含兩部分:BlockHeader和BlockData。其中BlockHeader主要存儲Block相關元數據,BlockData用來存儲具體數據。Block元數據中最核心的字段是BlockType字段,表示該Block的類型,HBase中定義了8種BlockType,每種BlockType對應的Block都存儲不一樣的內容,有的存儲用戶數據,有的存儲索引數據,有的存儲元數據(meta)。對於任意一種類型的HFileBlock,都擁有相同結構的BlockHeader,可是BlockData結構卻不盡相同。下表羅列了最核心的幾種BlockType。
1. Trailer Block
Trailer Block主要記錄了HFile的版本信息、各個部分的偏移值和尋址信息,圖爲Trailer Block的數據結構,其中只顯示了部分核心字段。
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結構如圖所示。
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儘量設置短的根本緣由。
布隆過濾器對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,對應的內存和邏輯結構如圖所示。
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中索引結構分爲兩種: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,如圖所示。
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的結構圖。
圖中,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結構的視角分析,不管是中間節點仍是葉子節點,其都擁有相同的結構,如圖所示。
和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原理與實踐》一書