HBase文件格式演變之路

Apache HBaseHadoop的分佈式開源的存儲管理工具,非常適合隨機實時的io操作。

我們知道,HadoopSequence File是一個順序讀寫,批量處理的系統。但是爲什麼HBase能做到隨機的,實時的io操作呢?

Hadoop底層使用Sequence File文件格式存儲,Sequence File允許以追加的方式增加k-vKey-Value數據,根據hdfsappend-only的特性,Sequence File不允許修改或刪除一個指定的數據。只有append操作是被允許的,而且你想要查找某個key,你只能遍歷文件,知道找到這個key爲止。

所以,在這個文件格式至上搭建的HBase系統,是如何搭建隨機讀寫,低訪問延遲的呢?

HBase 0.20之前-MapFile

上文中提到了MapFile,是一個以Sequence File爲基礎的文件格式。MapFile實際上是由兩個Sequence File組成的,/data存儲了數據,/index存儲了索引。

MapFile提供了一個按順序存儲的方式,每當NN是可配置的)條記錄寫入後,會將文件偏移地址寫入index文件;這就實現了快速查找,相比直接遍歷Sequence File,你可以直接遍歷index文件,index文件存儲了更少的文件句柄,一旦找到了文件塊所在的位置,可以直接跳到該文件塊查找;因此查找速度非常快。

但是還有另外兩個問題:

1. 如何刪除或更新一個k-v記錄;

2. 當插入數據不是有序的,如何使用MapFile

MapFile文件格式如下:


HBasekey包括:行鍵,列族,column qualifier,時間戳,屬性。

爲了解決刪除的問題,使用key中的type標記該記錄是否被刪除。

解決更新的問題,實際上是獲取時間戳更晚的記錄,正確的數據總會更接近文件的末尾。爲了解決無序數據的問題,hbase將插入的數據暫存在內存中,直到一個閾值到達,hbase會將內存中的數據存儲到MapFile中。hbase在內存中使用sorted ConcurrentSkipListMap數據結構存儲數據,每次到達閾值(hbase.hregion.memstore.flush.size)或到達內存上限(hbase.regionserver.global.memstore.upperLimit),都會將內存中的數據向一個新的mapFile寫入。由於每次flush操作都會寫入一個新的MapFile,這就意味着查找時,會橫跨多個文件,這也會耗費更多的資源和時間。

爲了避免在查找getscan掃描時橫跨過多的文件,hbase中有一個線程來執行文件合併的相關操作。當線程發現文件數量達到閾值(hbase.hstore.compaction.max)後,會有一個compaction進程來執行文件合併,將小的文件合併到一個大文件中。

hbase中有兩種合併模式,minormajorminor會合並兩個或多個較小的文件到一個大文件中。另外的major會合並所有的文件到一個文件中,並且執行一些清理,被刪除的數據將不會被寫入新的文件,並且重複的數據會被清除,只留下最新的有效數據。0.20以下版本的hbase使用上述的文件存儲方式,到0.20以後,HFile V1被引入替代了MapFile

HBase 0.20到0.92之前-HFile V1

從 HBase 0.20開始,HBase引入了HFile V1文件格式。HFile V1的具體格式如下:


KeyValue的具體格式如下:


上圖中,keytype有四種類型,分別是PutDelete、 DeleteColumnDeleteFamilyRowLength2個字節,Row長度不固定,ColumnFamilyLength2個字節,ColumnFamily長度不固定,ColumnQualifier長度不固定,TimeStamp4個字節,KeyType1個字節。之所以不記錄ColumnQualifier的長度是因爲可以通過其他字段計算得到。

HFile文件的長度可變,唯一固定的是File InfoTrailerTrailer存儲指向其他塊的指針,它在持久化數據到文件結束時寫入的,寫入後,該文件就會變成不可變的數據存儲文件。數據塊(data blocks)中存儲key-values,可以看做是一個MapFile。當block關閉操作時,第一個key會被寫入index中,index文件在hfile關閉操作時寫入。Hfile V1還增加兩個額外的元數據類型,MetaFileInfo。這兩個數據也是在hfileclose時被寫入的。

塊大小是由HColumnDescriptor設置的。該配置可以在建表時自己指定。默認是64KB。如果程序主要涉及順序訪問,則設置較大的塊大小更合適。如果程序主要涉及隨機訪問,則設置較小的塊大小更合適。不過較小的塊也導致更多的塊索引,有可能創建過程變得更慢(必須在每個塊結束的時候刷寫壓縮流,會導致一個FS I/O刷寫)。塊大小一般設置在8KB~1MB比較合適。

HBaseRegionserver的存儲文件中,使用Meta Block存儲了BloomFilter,使用FileInfo存儲了最大的SequenceIdMajor compaction key和時間跨度信息。這些信息在舊文件或非常新的文件中判斷key是否存在非常有用。

BloomFilter是一種空間效率很高的隨機數據結構,它利用位數組很簡潔地表示一個集合,並能判斷一個元素是否屬於這個集合。

HBase 0.92到0.98之前-HFile V2

hbase 0.92版本中,爲了改進在大數據存儲下的效率,HFile做了改變。HFile V1的主要問題是,你需要加載(load)所有的單片索引和BloomFilter到內存中。爲了解決這個問題,v2引入了多級索引和分塊BloomFilterHFile v2改進了速度,內存和緩存利用率。

HFile V2的具體格式如下:


上圖中,主要包括四個部分:Scanned Block(數據block)、Non-Scanned block(元數據block)、Load-on-open(在hbase運行時,HFile需要加載到內存中的索引、bloom filter元數據和文件信息)以及trailer(文件尾)。

V1的時候,在數據塊索引很大時,很難全部load到內存。假設每個數據塊使用默認大小64KB,每個索引項64Byte,這樣如果每臺及其上存放了60TB的數據,那索引數據就得有60G,所以內存的佔用還是很高的。然而,將這些索引以樹狀結構進行組織,只讓頂層索引常駐內存,其他索引按需讀取並通過LRU cache進行緩存,這樣就不用全部加載到內存了。

v2的最主要特性是內聯塊。主要思想是拆分了索引和BloomFilter到每個數據塊中,來解決整個文件的索引和BloomFilterload到內存中的問題

因爲索引被拆分到每個數據塊中,這就意味着每個數據塊都有自己的索引(leaf-index。每個數據塊中的最後一個key被當做節點組建了類似b+樹的多級索引結構


數據塊的頭信息中的Block MagicBlock Type替換,Block Type包含描述Block數據的一些信息,包括葉子索引,Bloom,元數據,根索引等。

具體實現來看,在寫入HFile時,在內存中會存放當前的inline block index,當inline block index大小達到一定閾值(比如128KB)時就直接flush到磁盤,而不再是最後做一次flush,這樣就不需要在內存中一直保持所有的索引數據。當所有的inline block index生成之後,HFile writer會生成更上一級的block index,它裏面的內容就是這些inline block indexoffset,依次遞歸,逐步生成更上層的block index,上層的包含的就是下層的offset,直到最頂層大小小於閾值時爲止。所以整個過程就是自底向上的通過下層index block逐步構建出上層index block

其他三個字段(compressed/uncompressed size and offset prev block)也被添加用於前後的快速查找

HFile V2中根據key查找數據的過程如下:

1)先在內存中對HFileroot索引進行二分查找,如果支持多級索引,則定位到leaf index,如果是單級索引,則定位到數據塊;

2)如果支持多級索引,則會從cache/hdfs中讀取leaf index,然後再進行二分查找,找到對應的數據塊;

3)從cache/hdfs中讀取數據塊;

4)在數據塊中遍歷查找對應的數據。

HBase 0.98到目前-HFile V3

HBase 0.98開始增加了對cell tags的支持,所以其HFile結構也發生了改變。HFile V3的格式只是在V2格式後增加了標籤部分。其他保持不變,所以對V2保持了兼容性。用戶可以從V2直接切換到V3。


轉載請註明出處:http://blog.csdn.net/iAm333

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