Sumary:
MemStore結構
KeyValue構成細節
HFile分析
Maven
項目例子使用了Maven來管理Dependency,要運行例子,需要有maven環境,后面提到的HFile,StoreFile,HStoreFile指的是同一樣東西,也就是HBase中Region每個CF對應的數據文件。
HBase一直有一個問題,困擾着我一段時間了.時而思考一下,終不得解。
問題發生於5月某天,在做大量Put測試去觀察MemStore的flush, HStoreFile的Split和Compact操作時,奇怪的事情發生了,默認MemStore的size為128M,headSize達到128M,進行flush之后,MemStore恢復為0M,生成snapshot寫hfile,最終生在hdfs的大小卻是30M左右。
百思不得奇怪,為什么會減少了這么多,壓縮?NO!
最近開始研究HFile的相關內容時,又想起了MemStore size和HFile size不一致問題,再次做試驗。最終找到了答案.
場景設置:
環境: 64位 CentOS VM 偽分布Hadoop ,standalone方式的HBase 連接本機hdfs. 4G內存 4核
Hadoop 2.3 HBase 0.98.1-hadoop2 HFileReader(Writer) V2
單個表,1個CF, 10W rows,即 70WKV,每個KV heapSize占100+byte左右,目測KV headSize會占有 70M
具體見測試代碼。
1. 執行App.java 結束后,查看后台 master:60010 ,memStore的內存達到 112.9M
圖1
2.通常,數據不會被flush至HFile中,重啟HBase,強制flush memStore,再次查看后台web gui.
stop-hbase.sh
start-hbase.sh
圖2
查看HFile只有26M。只有源數據的25%不到,很好,是我想要的結果。
也可以在stop后,直接使用hadoop 命令查看dfs中的文件大小 :hadoop fs -ls -R /hbase/data/default/t_sample/
圖3
至此,第一個小實驗做完了,結果很明顯,MemStore 112.9M flush到StoreFile中只有26M,不到1/4的容量,why?
首先我們看一個KeyValue的構成
圖4
從圖中可以得知KeyValue的細節,我們繼續一些有趣的小實驗。
1.構建一個KeyValue
public static void main(String[] args) throws Exception { final byte[] row = Bytes.toBytes("u1"); final byte[] family = Bytes.toBytes("info"); final byte[] qualifier = Bytes.toBytes("sex"); final byte[] value = Bytes.toBytes("M"); KeyValue kv = new KeyValue(row, family, qualifier, value); kv.setMvccVersion(System.currentTimeMillis()); System.out.println(kv.heapSize()); }
輸出: 96
順手抓了一幅圖如下:
圖5
結合KeyValue結構圖4及實際一個KeyValue的字節碼圖5我們分析一下:
第0-3個字節 為 int類型 Key的長度,在這里是 21
第4-7個字節 為 int類型 Value的長度,在這里只有1
第8-28個字節為Key的內容
8-9 short類型,指定了 rowKey的長度 值為 2.
10-11 對應的rowKey,轉為原String為u1
12 byte型的ColumnFamility Length,值為4
13-16 對應CF值,轉為原String為info
28為KeyType,由於我們創建KeyValue時沒有顯式指定,默認為Put(4)
20-27為8字節的Long型,對應timestamp.
所以剩下的為 17-19 三字節為Qualifier,轉為原String為sex
第29,即最后一字節,是Value內容 : "M"
好,假設我們KeyValue的真實內容,就是這堆長度為30的字節碼,我們簡單算一下, 30 * 70 * 10000 / 1024 / 1024 = 20M,這個數值是不是和我們的HFile的大小有點接近呢?我們試想一下,文件中還有一些Index、MetaBlock、Meta、FileInfo等信息,七七八八加起來,再加上本身其它column的 value就不止1字節,所以已經非常接近我們這26M的目標大小了。
好的,我們假設這些字節碼,就是直接存入到HFile中的,那么MemStore呢,存的又是哪樣?我們再簡單地算一下, 96 * 70*10000 / 1024 / 1024 = 64M,離 112.9M差了近一倍。
走另一條路,我們想另一個問題,再來看看,為什么KeyValue本身只有30byte,但是打印出來是在MemStore的heapSize是96?看看heapSize()方法。
public long heapSize() { int sum = 0; sum += ClassSize.OBJECT;// the KeyValue object itself 16字節 sum += ClassSize.REFERENCE;// pointer to "bytes" 8字節 sum += ClassSize.align(ClassSize.ARRAY);// "bytes" 24字節 sum += ClassSize.align(length);// number of bytes of data in the "bytes" array 以剛才的為例,30,會轉化為32字節. sum += 2 * Bytes.SIZEOF_INT;// offset, length 2字節 sum += Bytes.SIZEOF_LONG;// memstoreTS 8字節 return ClassSize.align(sum); }
OK,由於在內存中的原因,一個KeyValue對象除了本身實際內容外,還有 64byte是對象的內部實例等占用了部分空間,從而會這么大。
另外,我們查看MemStore的結構:
volatile KeyValueSkipListSet kvset;
KeyValue是放在SkipListSet中的,內部其實就是一個Map,那么我們每個KeyValue存在Map中其實是一個又一個是Entry.其中每一個Entry又占了64byte.所以,一個KeyValue占MemStore的空間大約是160bytes.
我們取168個字節為一個KeyValue大概算一算 168 * 70 * 10000 / 1024 / 1024 = 112.1 ,之所以取168其實也就是一行中7個KeyValue肉眼大約算出來的平均值,112.1M已經非常接近112.9M了.
假設我們用38* 70 * 10000 / 1024 / 1024來算,25.3M,更為精確點,所以結合二個size,我們假定,這就是它們的真實的內存大小和文件大小。
為了論證這一點,我們還是做一個實例。具體源碼見附件中。
我們直接通過HFileReader去讀取HDFS中的HFile,然后Scan里面所有的KeyValue進行統計。結果輸出為:
MemStoreSize:118391920,HFileSize:27723608,KeyValue Count:700000 轉化單位
MemStoreSize:112.9M,HFileSize:26.4M,KeyValue Count:700000
好了,真相大白,完全證明了上面的推斷是正確的。
項目源碼下載: http://files.cnblogs.com/bdifn/MemStore_HStore_sample.zip