1 WAL優化
一個Region有一個WAL實例,WAL實例啟動后再內存中維護了一個ConcurrentNavigableMap,是一個線程安全的并發集合,包含了很多個WAL文件的引用,當一個WAL文件寫滿之后就會開始下一個文件,WAL文件數量不斷增長知道達到一個閾值之后開始滾動。相關的優化參數有:
#Region中最大的WAL文件數量,默認值32(當前版本已舍棄)
hbase.regionserver.maxlogs
#HDFS塊大小,沒有默認值,如果不設置,則使用 hdfs的塊大小
hbase.regionserver.hlog.blocksize
#WAL文件大小因子,每一個WAL文件大小通過hdfs塊大小*WAL文件大小因子得出,默認0.95
hbase.regionserver.logroll.multiplier
1.1早期優化方式
早期WAL文件數量超過了maxlogs閾值就會引發WAL日志滾動,舊的日志會被清理掉,maxlogs的設置公式為:
(regionserver_heap_size * memstore fraction) /(logRollSize)
(REgionServer堆內存大小*memstore在JVM的堆內存中占的比例)/單個WAL文件的大小
WAL單個文件大小通過blocksize*影響因子。因為大多數人不知道這個公式,使用默認值導致很容易就超過32個log,造成很多不便。后來hbase將maxlogs的數值有HBase內部自己計算得出
1.2 新的優化方式
HBase內部自己的計算方式為:
Math.max(32,(regionserverHeapSize * memstoreSizeRatio *2 /logRollSize))
公式與之前的相差不大,只是分母多了個2倍
2 BlockCache的優化
一個RegionServer只有一個BlockCache,BlockCache不是存儲的必要部分,只是用來優化讀取性能的,讀取數據的時候,client會先從zk獲取Region信息,然后到RegionServer上查詢BlockCache是否有緩存數據,之后采取memstore和Region中查詢,如果獲取到了數據,返回同同事會將數據緩存一份到BlockCache。
BlockCache默認是開啟的,可以通過如下方式來關閉
alter 'mytable',CONFIGURATION =>{NAME =>'myCF',BLOCKCACHE => 'false'}
BlockCache的實現方案有四種:
2.1 LRUBlockCache
0.92版本之前只有該方法,Least Recently Used,近期最少使用算法的縮寫。模仿分代垃圾回收算法,LRU分為三個區域:
- single-access:單次讀取區,占比25%,block讀出后先放到該區域,當被讀取多次后升級到下一個區域
- multi-access:多次讀取區,占比50%,當一個被緩存到單次讀取區又被多次訪問,就會被升級到該區域
- in-memroy:放置設置為IN-MEMORY=true的列族讀取出來的block,占比25%。對應列族Block一開始就被放到了in-memory中,不經過單次讀取和多次讀取區域,并且具有最高存活時間,淘汰Block時候,這個區域Block最后被考慮到。
BlockCache無法關閉,只能調整大小,參數為:
hfile.block.cache.size LRUBlockCache:占用堆內存的比例,默認0.4
配置需要注意的是,Memstor+BlockCache占用的堆內存總量不能超過0.8,否則會報錯,留下20%作為機動空間,而Memstore的默認內存也是0.4,所以如果往大調整任何一個值,都必須調小另一個值。
BlockCache有很多優勢,通過內存做緩存調高讀取性能,但是BlockCache完全基于JVMHeap會導致隨著內存中的對象越來越多,每隔一段時間都會引發FULLGC。而為了避免FullGC,出現了基于堆外內存的BlockCache方案。
2.2 SlabCache(已廢棄)
所謂堆外內存(off-heap memory),就是不屬于JVM管理的內存范圍,也就是隸屬于原始內存的區域,堆外內存大小可以通過如下設置:
-XX:MaxDirectMemsorySize=60M
堆外內存最大的好處就是JVM幾乎不會停頓,也不用害怕回收時候業務卡住,但是堆外內存有幾點比較大的缺點:
- 堆外內存存儲的數據都是原始數據,如果是一個對象,比如序列化之后才能存儲,所以不能存儲太大太復雜的對象
- 堆外內存并不是在JVM的管理范圍,所以當內存泄漏的時候很不好排查問題
- 堆外內存由于用的是系統內存,使用太多可能導致物理內存溢出,或者因為開啟了虛擬內存導致了和磁盤的直接交互
SlabCache調用了nio的DirectByteBuffers,按照8:2的比例劃分為兩個區域:
- 80%:存放約等于一個BlockSize默認值的Block(64k)
- 20%:存放約等于兩個BlockSize默認值的Block(128K)
所以如果數據塊大于128K將不會被放入ScabCache中,同時SlabCache也是用LRU算法對緩存對象進行淘汰。所以有時候自定義了BlockSize后,會導致SlabCache放不進去數據,所以HBase使用了LRU和Slab組合的方式:
- 當一個Block被取出的時候同時放到SlabCache和LRUCache中
- 讀請求的時候,先查看LRUCache,如果沒有采取SlabCache中,如果查到了,就把Block放到LRUCache中。
感覺ScabCache就是LRUCachbe的二級緩存,所以管這個方案中的LRUCache為L1Cache,SlabCache為L2Cache。但實際上SlabCache對blockSize值定的太死,大部分請求還是直接走了LRU,所以對FullGC改善不明顯,后來被廢棄了。
2.3 BucketCache(同樣適用堆外內存)
BucketCache是阿里工程師設計的,借鑒了SlabCache,并且有了改善,
2.3.1 BucketCache的特點
- BucketCache上來直接分配了14種區域,注意是14種,大小分別是4、8、16、32、40、48、56、64、96、128、192、256、384、512KB的block,且種類列表可以通過hbase.bucketcache.bucket.sizes屬性來定義(種類之間用逗號分隔,不一定是14個)
- BucketCache存儲不一定使用堆外內存,可以使用堆(heap)、堆外(offheap)、文件(file)。可以通過hbase.bucketcache.ioengine為上述三個單詞中的一個做配置
- 每個Bucket的大小上限為block4,比如設置最大的Block類型為512KB,那么每個Bucket最大為512KB4=2018KB
- 要保證每一個block類型至少有一個Bucket的空間,否則直接報錯,按照每個Bucket的大小上限均分為多個Bucket,之前的例子至少要保證2048KB*14這么大的存儲空間
BucketCache可以自己劃分內存空間,自己管理內存空間,Block放進去的時候會考慮offset偏移量,所以內存碎片少,發生GC時間短。
另外,之所以會使用file作為存儲介質,是因為SSD硬盤的使用,極大地改進了SlabCache使用率低的問題。
2.3.2 BucketCache的相關配置
BucketCache默認是開啟的,可以使用如下配置關閉某個列族的BucketCache
hbase > alter 'table_name' CONFIGURATION => {C1 => 'true'}
意思是只使用一級緩存(LRUCache),不使用二級緩存。
BucketCache相關配置有:
#使用的存儲介質,可以為heap/offheap/file,默認offheap
hbase.bucketcache.ioengine
#是否打開組合模式,默認為true
hbase.bucketcache.combinedcache.enabled
#BucketCache所占大小,0.0-1.0代表站堆內存比例,大于1的值表示MB為單位的內存;默認為0.0,即關閉BucketCache
hbase.bucketcache.size
#定義所有的Block種類,單位為B,每一種類型必須是1024的整數倍
hbase.bucketcache.bucket.sizes
#該值不是在hbase-site.xml中配置,而是一個啟動參數,默認按需獲取堆外內存,如果配置了,就相當于設置了堆外內存上限
-XX:MaxDirectMemorySize
2.4 組合模式
在實際生產情況下,雖然BucketCache有很多好處,但是LRUCache性能遠比BucketCache強,因為這些二級Cache從速度和可管理性上始終無法和完全基于內存的LRUCache相比。BucketCache和LRU也并不是簡簡單單的一二級緩存集合,而是稱為組合模式,把不同類型的數據分別放到不同的存儲策略中。
Index Block會放到LRUCache中,Data Block放到了BucketCache中,所以查詢請求在BlockCache階段會先查詢一下LRUCache,會先到LRU查詢,然后到BucketCache中查詢出真正的數據。數據從一級緩存到二級緩存最后到硬盤,數據從小到大,存儲介質由快到慢,比較符合成本和性能規劃,而比較合理的存儲介質使用是:LRU用內存->BucketCache用SSD->HFile使用磁盤。
3 Memstore的優化
首先大家要明白,Memstore雖然是將數據存儲在內存中,但并不是為了加快讀取速度,而是為了維持數據結構。HDFS文件不支持修改,為了維持HBase中的數據是按照rowkey順序來存儲的,所以使用Memstore先對數據進行整理,之后才持久化到HDFS上。
3.1 Memstore的刷寫機制
Memstore的數據刷寫又叫flush,有5種情況會觸發刷寫。
3.1.1 大小達到了刷寫閾值
當單個Memstore占用的內存大小達到了hbase.hregion.memstore.flush.size的配置大小后就回觸發一次刷寫,默認128MB,生成一個HFile文件,因為刷寫是定期檢查的,所以無法及時的在數據到達閾值的一瞬間就觸發刷寫,有時候數據寫入非常快,會導致在檢查時間還沒到之前,Memstore的數據量就達到了刷寫閾值的好幾倍,就會導致觸發寫入阻塞機制,此時數據無法寫入Memstore,只能進行刷寫,影響比較嚴重。而阻塞機制有個閥值,定義如下:
hbase.hregion.memstore.flush.size * hbase.hregion.memstore.block.multiplier
hbase.hregion.memstore.flush.size是刷寫閥值,默認134217728=128MB
hbase.hregion.memstore.block.multiplier 是一個倍數,默認為4
也就是如果達到了刷寫閥值的4倍就會導致刷寫阻塞,會阻塞所有寫入該Store的寫請求,主要是為了應對數據如果繼續急速增長導致的更加嚴重的后果。在解決刷寫阻塞的時,并不能一味調大阻塞閥值,而是要綜合考慮HFile的相關參數設置。
3.1.2 RegionServer內部所有Memstore的總和達到了閥值
具體的閥值計算公式為:
globalMemStoreLimitLowMarkPercent* globalMemStoreSize
globalMemStoreLimitLowMarkPercent是全局memstore刷寫下限,是一個百分比,范圍0.0-1.0,默認0.95
globalMemStoreSize是全局memstore的容量,其計算方式為RegionServer的堆內存大小*hbase.regionserver.global.memstore.size,默認0.4,也就是堆內存的40%
總體來說,如果全局Memstore的總和達到了分配給Memstore最大內存的95%,就會導致全局刷寫,默認有40%的內存會分給Memstore。而當超過了Memstore的最大內存,也就是堆內存的40%就會觸發刷寫阻塞。如果有16G堆內存,默認情況下:
#達到該值會觸發刷寫
16*0.4*0.95=0.608
#達到該值會觸發刷寫阻塞
16*0.4=6.4
3.1.3 WAL的數量大于maxLogs
當WAL的數量大于maxLogs的時候,也會觸發一次刷寫,會報警一下,該操作一定不會導致刷寫阻塞。
maxLogs的計算公式為:
Math.max(32,(regionserverHeapSize * memstoreSizeRatio * 2 / logRollSize))
該操作主要為了騰出內存空間。
3.1.4 Memstore達到刷寫時間間隔
默認時間為1小時,如果設置為0則關閉自動定時刷寫。配置參數為:
hbase.regionserver.optionalcacheflushinterval 默認為3600000,1個小時
3.1.5 手動觸發刷寫
JavaAPI Admin接口提供的方法為:
#對單個表刷寫
flush(TableName tableName)
#對單個Region刷寫
flushRegion(byte[] regionName)
hbase shell命令:
#單表刷寫
flush 'tableName'
#單個Region刷寫
flush 'regionname'
3.2 總結
memStore的優化可以主要關注放置觸發阻塞機制了,要合理設置Memstore的內存,以及RegionServer的堆內存。