和其余数据库同样,优化IO也是HBase提高性能的不二法宝,而提供缓存更是优化的重中之重。html
根据二八法则,80%的业务请求都集中在20%的热点数据上,所以将这部分数据缓存起就能够极大地提高系统性能。算法
HBase在实现中提供了两种缓存结构:MemStore和BlockCache。其中MemStore称为写缓存,HBase执行写操做首先会将数据写入MemStore,并顺序写入HLog,等知足必定条件后统一将MemStore中数据刷新到磁盘,这种设计能够极大地提高HBase的写性能。不只如此,MemStore对于读性能也相当重要,假如没有MemStore,读取刚写入的数据就须要从文件中经过IO查找,这种代价显然是昂贵的!BlockCache称为读缓存,HBase会将一次文件查找的Block块缓存到Cache中,以便后续同一请求或者邻近数据查找请求,能够直接从内存中获取,避免昂贵的IO操做。数据库
了解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.jvm
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.优化
将上面的缓存分为两类:
当使用BucketCache,数据缓存的最小单位是Block。
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。
LRU缓存把最近最少使用的数据移除,让给最新读取的数据。而每每最常读取的,也是读取次数最多的,因此,利用LRU缓存,咱们可以提升系统的performance.
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);
使用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产生大量内存碎片。
每一个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。
其中实线表示cache block流程,虚线表示get block流程。
BucketCache默认有三种工做模式:
heap、offheap和file;
三者不一样之处是对应的最终存储介质有所不一样,即上述所讲的IOEngine有所不一样。
heap模式和offheap模式,使用内存做为最终存储介质。
分配内存时,offheap快;读内存时,heap快。heap模式受GC影响
file模式
它使用Fussion-IO或者SSD等做为存储介质,相比昂贵的内存,这样能够提供更大的存储容量,所以能够极大地提高缓存命中率。
<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>
<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>
<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>
在’缓存所有命中’场景下,LRU君可谓完胜CBC君。所以若是总数据量相比JVM内存容量很小的时候,选择LRU君;
在全部其余存在缓存未命中状况的场景下, LRU君的GC性能几乎只有CBC君的1/3,而吞吐量、读写延迟、IO、CPU等指标二者基本至关,所以建议选择CBC。
之因此在’缓存所有命中’场景下LRU的各项指标完胜CBC,而在’缓存大量未命中’的场景下,LRU各项指标与CBC基本至关,是由于HBase在读取数据的时候,若是都缓存命中的话,对于CBC,须要将堆外内存先拷贝到JVM内,而后再返回给用户,流程比LRU君的堆内内存复杂,延迟就会更高。而若是大量缓存未命中,内存操做就会占比很小,延迟瓶颈主要在于IO,使得LRU和CBC二者各项指标基本至关。
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模式。
BlockCache先使用默认参数LRUBlockCache
最佳参数:根据监控指标作优化,加大内存并兼顾GC时间。