2/2)HBase GC的前生今世 – 演進(jìn)篇

HBase GC的前生今世 – 演進(jìn)篇 – 有態(tài)度的HBase/Spark/BigData http://hbasefly.com/2016/05/29/hbase-gc-2/

最原始的HBase CMS GC相當(dāng)嚴(yán)重,經(jīng)常會因?yàn)樗槠^多導(dǎo)致Promotion Failure,嚴(yán)重影響業(yè)務(wù)的讀寫請求。幸運(yùn)的是,HBase并沒有止步不前,很多優(yōu)化方案相繼被提出并貢獻(xiàn)給社區(qū),本文要介紹的就是幾個比較重要的核心優(yōu)化,分別是針對Memstore所作的兩個優(yōu)化:Thread-Local Allocation Buffer和MemStore Chunk Pool 以及針對BlockCache所作的優(yōu)化:BucketCache方案。在詳細(xì)介紹這幾個優(yōu)化之前有必要簡單介紹一下HBase GC優(yōu)化的目標(biāo),很直觀的,第一是要盡量避免長時間的Full GC,避免影響用戶的讀寫請求;第二是盡量減少GC時間,提高讀寫性能;接著分別來看HBase針對GC所做的各種優(yōu)化:

MemStore GC優(yōu)化一 - Thread-Local Allocation Buffer
HBase數(shù)據(jù)寫入操作實(shí)際上并沒有直接將數(shù)據(jù)寫入磁盤,而是先寫入內(nèi)存并順序?qū)懭際Log,之后等待滿足某個特定條件后統(tǒng)一將內(nèi)存中的數(shù)據(jù)刷新到磁盤。一個RegionServer通常由多個Region組成,每張Region通常包含一張表的多個列族,而每個列族對應(yīng)一塊內(nèi)存區(qū)域,這塊內(nèi)存被稱為MemStore,很顯然,一個RegionServer會由多個Region構(gòu)成,一個Region會由多個MemStore構(gòu)成。
最原始的HBase版本存在很嚴(yán)重的內(nèi)存碎片,經(jīng)常會導(dǎo)致長時間的Full GC,其中最核心的問題就出在MemStore這里。因?yàn)橐粋€RegionServer由多個Region構(gòu)成,不同Region的數(shù)據(jù)寫入到對應(yīng)Memstore,在JVM看來其實(shí)是混合在一起寫入Heap的,此時假如Region1上對應(yīng)的所有MemStore執(zhí)行落盤操作,就會出現(xiàn)下圖所示場景:

l1

為了優(yōu)化這種內(nèi)存碎片可能導(dǎo)致的Full GC,HBase借鑒了Arena Allocation內(nèi)存管理方式,它通過順序化分配內(nèi)存、內(nèi)存數(shù)據(jù)分塊等特性使得內(nèi)存碎片更加粗粒度,有效改善Full GC情況;

具體實(shí)現(xiàn)原理如下:

  1. 每個MemStore會實(shí)例化出來一個MemStoreLAB
  2. MemStoreLAB會申請一個2M大小的Chunk數(shù)組和一個Chunk偏移量,初始值為0
  3. 當(dāng)一個KeyValue值插入MemStore后,MemStoreLAB會首先通過KeyValue.getBuffer()取得data數(shù)組,并將data數(shù)組復(fù)制到Chunk數(shù)組中,之后再將Chunk偏移量往前移動data.length
  4. 如果當(dāng)前Chunk滿了之后,再調(diào)用new byte[ 2 * 1024 * 1024]申請一個新的Chunk

很顯然,通過申請2M大小的Chunk可以使得內(nèi)存碎片更加粗粒度,官方在優(yōu)化前后通過設(shè)置 -xx:PrintFLSStatistics = 1 統(tǒng)計(jì)了老生代的Max Chunk Size分別隨時間的變化曲線,如下圖所示:


l2

l3

由上圖可以看出,未優(yōu)化前碎片會大量出現(xiàn)導(dǎo)致頻繁的Full GC,優(yōu)化后雖然依然會產(chǎn)生大量碎片,但是最大碎片大小一直會維持在1e+08左右,極大地降低了Full GC頻率。

MemStore GC優(yōu)化二 – MemStore Chunk Pool
然而一旦一個Chunk寫滿之后,系統(tǒng)就會重新申請一個新的Chunk,這些Chunk大部分都會經(jīng)過多次YGC之后晉升到老生代,如果某個Chunk再沒有被引用就會被JVM垃圾回收。很顯然,不斷申請新的Chunk會導(dǎo)致YGC頻率不斷增多,YGC頻率增加必然會導(dǎo)致晉升到老生代的Chunk增多,進(jìn)而增加CMS GC發(fā)生的頻率。如果這些Chunk能夠被循環(huán)利用,系統(tǒng)就不需要申請新的Chunk,這樣就會使得YGC頻率降低,晉升到老生代的Chunk就會減少,CMS GC發(fā)生的頻率就會降低。這就是MemStore Chunk Pool的核心思想,具體實(shí)現(xiàn)如下:

  1. 系統(tǒng)會創(chuàng)建一個Chunk Pool來管理所有未被引用的chunks,這些chunk就不會再被JVM當(dāng)作垃圾回收掉了
  2. 如果一個Chunk沒有再被引用,將其放入Chunk Pool
  3. 如果當(dāng)前Chunk Pool已經(jīng)達(dá)到了容量最大值,就不會再接納新的Chunk
  4. 如果需要申請新的Chunk來存儲KeyValue,首先從Chunk Pool中獲取,如果能夠獲取得到就重復(fù)利用,如果為null就重新申請一個新的Chunk

官方針對該優(yōu)化也進(jìn)行了簡單的測試,使用jstat -gcutil對優(yōu)化前后的JVM GC情況進(jìn)行了統(tǒng)計(jì),具體的測試條件和測試結(jié)果如下所示:

測試條件:

HBase版本:0.94JVM參數(shù):-Xms4G -Xmx4G -Xmn2G單條數(shù)據(jù)大?。篟ow size=50 bytes, Value size=1024 bytes實(shí)驗(yàn)方法:50 concurrent theads per client, insert 10,000,000 rows

測試結(jié)果:

YGC
YGCT
FGC
FGCT
GCT

優(yōu)化前
747
36.503
48
2.492
38.995

優(yōu)化后
711
20.344
4
0.284
20.628

很顯然,經(jīng)過優(yōu)化后YGC時間降低了40+%左右,F(xiàn)GC的次數(shù)以及時間更是大幅下降。

BlockCache優(yōu)化-BucketCache方案
對于需要深入了解HBase針對BlockCache所做的GC優(yōu)化的朋友,強(qiáng)烈建議首先閱讀之前的3篇 BlockCache 系列博文:part1 , part2 和 part3。文中重點(diǎn)介紹了BlockCache的兩種實(shí)現(xiàn)方案:LRUBlockCache和BucketCache。

其中LRUBlockCache是目前HBase的默認(rèn)方案,這種方案會將內(nèi)存區(qū)分為3個部分:single-access區(qū)、mutil-access區(qū)以及in-memory區(qū),一個Block塊從HDFS中加載出來之后首先放入single區(qū),后續(xù)如果有多次請求訪問到這塊數(shù)據(jù)的話,就會將這塊數(shù)據(jù)移到mutil-access區(qū)。隨著Block數(shù)據(jù)從single-access區(qū)晉升到mutil-access區(qū),基本就伴隨著對應(yīng)的內(nèi)存對象從young區(qū)到old區(qū) ,晉升到old區(qū)的Block被淘汰后會變?yōu)閮?nèi)存垃圾,最終由CMS回收掉,CMS回收之后必然會產(chǎn)生大量的內(nèi)存碎片,碎片空間一直累計(jì)就會產(chǎn)生臭名昭著的Full GC。

為了減少頻繁 CMS GC 產(chǎn)生的碎片問題,社區(qū)采納了阿里開發(fā)者的新方案:BucketCache。這種方案還是采用“將小碎片整理為大碎片”的思路,由程序在初始化的時候就申請了很多大小為2M的Bucket,數(shù)據(jù)Block的Get/Cache動作只是對這片空間的訪問/覆寫,CMS碎片會自然大大降低。BucketCache有三種工作模式:heap、offheap以及file,其中heap模式表示將數(shù)據(jù)存儲在JVM堆內(nèi)存,offheap模式表示將數(shù)據(jù)Block存儲到操作系統(tǒng)內(nèi)存,file模式表示將數(shù)據(jù)Block存儲到類似于SSD的外部高速緩存上;很顯然,offheap模式和file模式根本沒有將數(shù)據(jù)Block存在JVM堆內(nèi)存,所以幾乎不會出現(xiàn)Full GC,而heap模式即使數(shù)據(jù)存儲在JVM堆內(nèi)存,也會因?yàn)閮?nèi)存由程序獨(dú)立管理大大降低內(nèi)存碎片。

針對BlockCache的兩種實(shí)現(xiàn)方案,分別簡單地對內(nèi)存碎片產(chǎn)生情況和GC情況進(jìn)行了統(tǒng)計(jì),結(jié)果如下:


l4

YGC
YGCT(s)
FGC
FGCT(s)
GCT(s)

LRUBlockCache
79
26.8
75
13
39.8

BucketCache
171
11
1
0.462
11.462

從結(jié)果可以看出,BucketCache大大減少了碎片的產(chǎn)生,而且YGC和FGC時間也極大地得到了改善。需要注意的是,此結(jié)論是在部分緩存未命中的情況下得出的,緩存全部命中的場景結(jié)果會有所不同。

總結(jié)
所有構(gòu)建在JVM上的應(yīng)用或多或少都會受到GC的影響,尤其對于大內(nèi)存系統(tǒng)更是如此,HBase也不例外。針對GC問題,一方面我們期待JVM能夠做出更多地改進(jìn)和優(yōu)化,另一方面,我們也可以從內(nèi)存管理方面進(jìn)行更多地探索,不斷優(yōu)化內(nèi)存的使用。HBase在0.98之后的版本還不斷針對GC進(jìn)行著優(yōu)化,后續(xù)再進(jìn)行補(bǔ)充!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容