hbase實踐(十六) BlockCache
0 引言
和其他資料庫一樣,優化IO也是HBase提升效能的不二法寶,而提供快取更是優化的重中之重。
根據二八法則,80%的業務請求都集中在20%的熱點資料上,因此將這部分資料快取起就可以極大地提升系統性能。
HBase在實現中提供了兩種快取結構:MemStore和BlockCache。其中MemStore稱為寫快取,HBase執行寫操作首先會將資料寫入MemStore,並順序寫入HLog,等滿足一定條件後統一將MemStore中資料重新整理到磁碟,這種設計可以極大地提升HBase的寫效能。不僅如此,MemStore對於讀效能也至關重要,假如沒有MemStore,讀取剛寫入的資料就需要從檔案中通過IO查詢,這種代價顯然是昂貴的!BlockCache稱為讀快取,HBase會將一次檔案查詢的Block塊快取到Cache中,以便後續同一請求或者鄰近資料查詢請求,可以直接從記憶體中獲取,避免昂貴的IO操作。
1. Contents of the BlockCache
瞭解BlockCache中存放的內容,可以幫助我們更好設計BlockCache的大小。
Your data: Each time a Get or Scan operation occurs, the result is added to the BlockCache if it was not already cached there. If you use the BucketCache, data blocks are always cached in the BucketCache.
Row keys: When a value is loaded into the cache, its row key is also cached. This is one reason to make your row keys as small as possible. A larger row key takes up more space in the cache.
hbase:meta: The hbase:meta catalog table keeps track of which RegionServer is serving which regions. It can consume several megabytes of cache if you have a large number of regions, and has in-memory access priority, which means HBase attempts to keep it in the cache as long as possible.
Indexes of HFiles: HBase stores its data in HDFS in a format called HFile. These HFiles contain indexes which allow HBase to seek for data within them without needing to open the entire HFile. The size of an index is a factor of the block size, the size of your row keys, and the amount of data you are storing. For big data sets, the size can exceed 1 GB per RegionServer, although the entire index is unlikely to be in the cache at the same time. If you use the BucketCache, indexes are always cached on-heap.
Bloom filters: If you use Bloom filters, they are stored in the BlockCache. If you use the BucketCache, Bloom filters are always cached on-heap.
The sum of the sizes of these objects is highly dependent on your usage patterns and the characteristics of your data. For this reason, the HBase Web UI and Cloudera Manager each expose several metrics to help you size and tune the BlockCache.
將上面的快取分為兩類:
- 資料本身:your data、rowkeys
- 索引: hbase:meta、Indexes of HFiles、Bloom filters。
當使用BucketCache,資料快取的最小單位是Block。
2. BlockCache
BlockCache是Region Server級別的,一個Region Server只有一個Block Cache,在Region Server啟動的時候完成Block Cache的初始化工作。到目前為止,HBase先後實現了3種Block Cache方案,LRUBlockCache是最初的實現方案,也是預設的實現方案;HBase 0.92版本實現了第二種方案SlabCache,見HBASE-4027;HBase 0.96之後官方提供了另一種可選方案BucketCache,見HBASE-7404。
2.1 LRU Least-Recently-Used
LRU快取把最近最少使用的資料移除,讓給最新讀取的資料。而往往最常讀取的,也是讀取次數最多的,所以,利用LRU快取,我們能夠提高系統的performance.
2.2 LRUBlockCache
LRUBlockCache將快取分為三塊:single-access區、mutil-access區、in-memory區,分別佔到整個BlockCache大小的25%、50%、25%。
memory區表示資料可以常駐記憶體,一般用來存放訪問頻繁、資料量小的資料,比如元資料,使用者也可以在建表的時候通過設定列族屬性IN-MEMORY= true將此列族放入in-memory區。很顯然,這種設計策略類似於JVM中young區、old區以及perm區。無論哪個區,系統都會採用嚴格的Least-Recently-Used演算法
LruBlockCache內部是通過一個ConcurrentHashMap來儲存所有cache的block的。
/** Concurrent map (the cache) */
private final Map<BlockCacheKey,LruCachedBlock> map;
map = new ConcurrentHashMap<BlockCacheKey,LruCachedBlock>(mapInitialSize,
mapLoadFactor, mapConcurrencyLevel);
- LRU方案優缺點:
- 優點:LRU方案使用JVM提供的HashMap管理快取,簡單有效。
- 缺點:Full GC:在大記憶體條件下,一次Full GC很可能會持續較長時間,甚至達到分鐘級別
2.2 SlabCache
使用Java NIO DirectByteBuffer技術實現了堆外記憶體儲存。
SlabCache有兩個快取區,分別佔整個BlockCache大小的80%和20%,每個快取區分別儲存固定大小的Block塊:
前者主要儲存小於等於64K大小的Block,後者儲存小於等於128K Block,如果一個Block太大就會導致兩個區都無法快取。
Q: 使用者設定BlockSize = 256K怎麼辦?
HBase實際實現中將SlabCache和LRUBlockCache搭配使用,稱為DoubleBlockCache。一次隨機讀中,一個Block塊從HDFS中加載出來之後會在兩個Cache中分別儲存一份;快取讀時首先在LRUBlockCache中查詢,如果Cache Miss再在SlabCache中查詢,此時如果命中再將該Block放入LRUBlockCache中。
SlabCache存在的問題?
SlabCache設計中固定大小記憶體設定會導致實際記憶體使用率比較低,
而且使用LRUBlockCache快取Block依然會因為JVM GC產生大量記憶體碎片。
2.3 BlockCache
2.3.1 BucketCache記憶體組織形式
每個bucket會有一個baseoffset變數和一個size標籤,其中baseoffset變量表示這個bucket在實際物理空間中的起始地址,因此block的實體地址就可以通過baseoffset和該block在bucket的偏移量唯一確定;而size標籤表示這個bucket可以存放的block塊的大小。
預設14個不同大小的Bucket :4, 8, 16, 32, 40, 48, 56, 64, 96, 128, 192, 256, 384, 512 KB
優點:可以快取不同大小的資料塊Block。
2.3.2 使用BucketAllocator類實現對Bucket的組織管理
- HBase會根據每個bucket的size標籤對bucket進行分類,相同size標籤的bucket由同一個BucketSizeInfo管理;
- HBase在啟動的時候為每種size標籤分配一個bucket,最後所有剩餘的bucket都分配最大的size標籤,預設分配(512+1)K
- Bucket的size標籤可以動態調整,比如64K的block數目比較多,65K的bucket被用完了以後,其他size標籤的完全空閒的bucket可以轉換成為65K的bucket,但是至少保留一個該size的bucket。
2.3.3 Block快取寫入、讀取流程
- RAMCache是一個儲存blockkey和block對應關係的HashMap;
- WriteThead是整個block寫入的中心樞紐,主要負責非同步的寫入block到記憶體空間;
- BucketAllocator主要實現對bucket的組織管理,為block分配記憶體空間;
- IOEngine是具體的記憶體管理模組,主要實現將block資料寫入對應地址的記憶體空間;
- BackingMap也是一個HashMap,用來儲存blockKey與對應實體記憶體偏移量的對映關係,用來根據blockkey定位具體的block;
其中實線表示cache block流程,虛線表示get block流程。
2.3.4 BucketCache工作模式
BucketCache預設有三種工作模式:
heap、offheap和file;
三者不同之處是對應的最終儲存介質有所不同,即上述所講的IOEngine有所不同。
heap模式和offheap模式,使用記憶體作為最終儲存介質。
分配記憶體時,offheap快;讀記憶體時,heap快。heap模式受GC影響
file模式
它使用Fussion-IO或者SSD等作為儲存介質,相比昂貴的記憶體,這樣可以提供更大的儲存容量,因此可以極大地提升快取命中率。
3. BlockCache引數設定
- heap模式
<hbase.bucketcache.ioengine>heap</hbase.bucketcache.ioengine>
//bucketcache佔用整個jvm記憶體大小的比例
<hbase.bucketcache.size>0.4</hbase.bucketcache.size>
//bucketcache在combinedcache中的佔比
<hbase.bucketcache.combinedcache.percentage>0.9</hbase.bucketcache.combinedcache.percentage>
- offheap模式
<hbase.bucketcache.ioengine>offheap</hbase.bucketcache.ioengine>
<hbase.bucketcache.size>0.4</hbase.bucketcache.size>
<hbase.bucketcache.combinedcache.percentage>0.9</hbase.bucketcache.combinedcache.percentage>
- file模式
<hbase.bucketcache.ioengine>file:/cache_path</hbase.bucketcache.ioengine>
//bucketcache快取空間大小,單位為MB
<hbase.bucketcache.size>10 * 1024</hbase.bucketcache.size>
//快取記憶體路徑
<hbase.bucketcache.persistent.path>file:/cache_path</hbase.bucketcache.persistent.path>
4. BlockCache使用場景
在’快取全部命中’場景下,LRU君可謂完勝CBC君。因此如果總資料量相比JVM記憶體容量很小的時候,選擇LRU君;
在所有其他存在快取未命中情況的場景下, LRU君的GC效能幾乎只有CBC君的1/3,而吞吐量、讀寫延遲、IO、CPU等指標兩者基本相當,因此建議選擇CBC。
之所以在’快取全部命中’場景下LRU的各項指標完勝CBC,而在’快取大量未命中’的場景下,LRU各項指標與CBC基本相當,是因為HBase在讀取資料的時候,如果都快取命中的話,對於CBC,需要將堆外記憶體先拷貝到JVM內,然後再返回給使用者,流程比LRU君的堆內記憶體複雜,延遲就會更高。而如果大量快取未命中,記憶體操作就會佔比很小,延遲瓶頸主要在於IO,使得LRU和CBC兩者各項指標基本相當。
- BlockCache不同配置場景
1)如果請求的資料比較符合快取,命中率比較高,使用LRUBlockCache方式會比CombinedBlockCache的吞吐量高上20%(但也會犧牲一些垃圾回收)。
1)如果需要快取的資料超過堆大小的情況下,推薦使用Block Cache下的off-heap。
2)當scan獲取資料時,可以通過setCacheBlocks方法來設定是否使用block cache,對於頻繁訪問的行才建議使用block cache。
3)對於MapReduce的Scan作為輸入任務,應該設定為setCacheBlocks(false)。
4)如果快取遇到持續高的驅逐速率,這會導致LruBlockCache大量的垃圾回收,請使用CombinedBlockCache。
5)CombinedBlockCache在固態磁碟上使用file檔案模式具有更好的垃圾回收,但吞吐量低於CombinedBlockCache使用offheap模式。
5. 小結
BlockCache先使用預設引數LRUBlockCache
最佳引數:根據監控指標做優化,加大記憶體併兼顧GC時間。