BlockCache综述
读取数据时,如果每次都是访问hfile,这样其性能是很低的,特别是在随机小数据量读的场景下。为提高IO性能,hbase提供了缓存的机制BlockCache。根据帕列托法则(二八法则),我们并不需要将所有数据都进行缓存,能保证热点数据(20%)缓存起来即可。这样就可以大大提高读取的效率。
BlockCache是RegionServer级别的,即每个RegionServer会有一个BlockCache,BlockCache会将要读取的block放到内存中,以便后续的访问。HBase提供了4种BlockCache的方案
- 最初的LruBlockCache
- SlabCache,见HBASE-4027,0.92版本提供
- BucketCache,见HBASE-7404,0.95版本
- ExternalBlockCache,见HBASE-13170,1.10版本
其中SlabCache由于以下原因,在1.0版本后被废弃了HBASE-11307
- 使用了单一cache大小,由于有多种类型的blocksize这样会导致内存使用率低,特别是在使用了DataBlockEncoding的情况下。
- 使用了DoubleBlockCache,block会在SlabCache和LRUBlockCache都缓存一份,当在SlabCache中命中block时会把block放到LRUBlockCache中,这样会导致CMS GC和造成堆碎片。比单独用LRUBlockCache没有改善。
- 堆外内存的性能没有堆内存高
LruBlockCache
LruBLockCache是在JVM的heap中的,其中的block有3个级别的优先级分别为single-access, multiple-accesses, and in-memory,这里使用多种策略可以避免由于有大批量的scan操作时,导致缓存全部被替换的问题。这3种策略默认占空间比例为0.25、0.5、0.25.如果表的family中有定义IN_MEMORY=true则该family下的块会设置为in-memory,一般访问特别频繁的数据可以这样设置,如base:meta表.而其他的block首次访问的时候为single-access。后续如果再次访问在cache中的这个block就会改为multiple-accesses。如果这些块的大小超过了上线,就会触发回收处理,使用的是LRU(Least-Recently-Used)算法。如果只使用LruBlockCache,在内存较大时会存在GC的问题导致服务中断。
BucketCache
BucketCache解决了SlabCache中存在的问题,首先其支持多种Cache方式,有heap、offheap和file。
- heap为使用jvm中的heap
- offheap为使用堆外内存
- file为使用文件的方式,如虚拟内存文件系统tmpfs或者高速SSD盘
另外支持多种不同大小的bucket,以适应不同大小的blocksize。可以通过参数hbase.bucketcache.bucket.sizes来配置不同bucket的大小。默认有14种大小从5k~513k。而使用堆外内存的性能问题在新版本中解决HBASE-11425
ExternalBlockCache
ExternalBlockCache为使用外部的其他缓存服务来提供,如memcached和redis等。这个可以避免在服务器挂掉或者升级的时候缓存全部失效的问题。
在使用方式上可以单独使用LRUBlockCache,或者组合起来使用,多级缓存的方式。LruBlockCache为一级缓存,BucketCache或者ExternalBlockCache为二级缓存。
见如下代码:
// CacheConfig.java
public static synchronized BlockCache instantiateBlockCache(Configuration conf) {
if (GLOBAL_BLOCK_CACHE_INSTANCE != null) return GLOBAL_BLOCK_CACHE_INSTANCE;
if (blockCacheDisabled) return null;
MemoryUsage mu = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
LruBlockCache l1 = getL1(conf, mu);
// blockCacheDisabled is set as a side-effect of getL1(), so check it again after the call.
if (blockCacheDisabled) return null;
BlockCache l2 = getL2(conf, mu);
if (l2 == null) {
GLOBAL_BLOCK_CACHE_INSTANCE = l1;
} else {
boolean useExternal = conf.getBoolean(EXTERNAL_BLOCKCACHE_KEY, E