Hbase 的cache block

来源:互联网 发布:算法统宗 在线阅读 编辑:程序博客网 时间:2024/05/17 07:58

HBase上Regionserver的内存分为两个部分,一部分作为Memstore,主要用来写;另外一部分做BlockCache,用来读,当然Memstore也有读的功效,不过由于Hbase的scan机制,从Memsotre读到数据的效果一般。

 

今天主要来分析下Hbase的BlockCache机制,并且阐述其中碰到的一个RTE异常。

 

话不多说,首先来看看Hbase的存储机制。

其实际存储文件是HFile格式的,这是一个HDFS上的Seq的二进制流

HFILE由以下几部分组成

DATABLOCK    DATABLOCK 。。。标志 METABLOCK   METABLOCK  。。。DATAINDEX  METAINDEX  TRAILER

TRAILER为头指针,指向DATAINDEX和METAINDEX,DATAINDEX和METAINDEX分别指向BLOCK的起始位置

DATABLOCK的结构是keyvalue键值对,具体的结构就不分析了

 

LRU顾名思义就是最近最久未使用方法,查找到内存中最近最久未使用的进行淘汰

RS在启动的时候会启动一个线程专门进行LRU淘汰如下所示:

 

Java代码  收藏代码
  1. public void run() {  
  2.       while(true) {  
  3.         synchronized(this) {  
  4.           try {  
  5.             this.wait();  
  6.           } catch(InterruptedException e) {}  
  7.         }  
  8.         LruBlockCache cache = this.cache.get();  
  9.         if(cache == nullbreak;  
  10.         cache.evict();  
  11.       }  
  12.     }  

 该段代码是一个循环等待LRU进程唤醒条件的发生,当该条件满足时会调用cache的evict()方法进行淘汰。

 

那么产生的条件是什么呢

整个RS的LRU维护一个HashMap<string,Block>,String 是一个blockpath,由storefilepath+标记+blocknum组成
当有读请求发生时首先会对当前storefile的read对象,synchronized (metaIndex.blockKeys[block])
然后get,若无则加进map中。有则直接返回Block

 

当加入map以后map的size达到一个阈值,此时:

Java代码  收藏代码
  1. public void evict() {  
  2.     synchronized(this) {  
  3.       this.notify(); // FindBugs NN_NAKED_NOTIFY  
  4.     }  
  5.   }  

 该函数会通知LRU线程进行淘汰。淘汰的规则如下:

 

将MAP中的数据装入3个桶:In-Memory,muti,single 。3个桶都有容量,且各自维护一个list用来存放放入其中的Block

删除的是批量删除的形式,即如果Map的size大于阈值A,那么此时会释放到一个MAP的初始值大小。

 

首先选择将要删除的桶,超过桶容量最大的优先选择删除算法如下所示

 

Java代码  收藏代码
  1. while((bucket = bucketQueue.poll()) != null) {  
  2.        long overflow = bucket.overflow();  
  3.        if(overflow > 0) {  
  4.          long bucketBytesToFree = Math.min(overflow,  
  5.            (bytesToFree - bytesFreed) / remainingBuckets);  
  6.          bytesFreed += bucket.free(bucketBytesToFree);  
  7.        }  
  8.        remainingBuckets--;  
  9.      }  

 

cache可以高效地提升hbase的读TPS,在hbase中具有重要的作用。

Memstore则是写操作的关键,二者在hbase都有重要的作用,当然是越大越好。不过hbase规定二者之和不得超过可用内存的0.8呗,否则会产生不可预知的错误。

 

 

下面来看看碰到的一个RTE异常

Java代码  收藏代码
  1. synchronized (metaIndex.blockKeys[block]) {  
  2.         metaLoads++;  
  3.         // Check cache for block.  If found return.  
  4.         if (cache != null) {  
  5.           ByteBuffer cachedBuf = cache.getBlock(name + "meta" + block,  
  6.               cacheBlock);  
  7.           if (cachedBuf != null) {  
  8.             // Return a distinct 'shallow copy' of the block,  
  9.             // so pos doesnt get messed by the scanner  
  10.             cacheHits++;  
  11.             return cachedBuf.duplicate();  
  12.           }  
  13.           // Cache Miss, please load.  
  14.         }  
  15.   
  16.         ByteBuffer buf = decompress(metaIndex.blockOffsets[block],  
  17.           longToInt(blockSize), metaIndex.blockDataSizes[block], true);  
  18.         byte [] magic = new byte[METABLOCKMAGIC.length];  
  19.         buf.get(magic, 0, magic.length);  
  20.   
  21.         if (! Arrays.equals(magic, METABLOCKMAGIC)) {  
  22.           throw new IOException("Meta magic is bad in block " + block);  
  23.         }  
  24.   
  25.         // Create a new ByteBuffer 'shallow copy' to hide the magic header  
  26.         buf = buf.slice();  
  27.   
  28.         readTime += System.currentTimeMillis() - now;  
  29.         readOps++;  
  30.         readData +=longToInt(blockSize);  
  31.   
  32.         // Cache the block  
  33.         if(cacheBlock && cache != null) {  
  34.           cache.cacheBlock(name + "meta" + block, buf.duplicate(), inMemory);  
  35.         }  
  36.   
  37.         return buf;  
  38.       }  

 

这段代码逻辑性本身是没有问题,但是当出现以下情况时就有触发异常

LRU维护一个HashMap<string,Block>,String 是一个blockpath,由storefilepath+标记+blocknum组成。

当有读请求发生时首先会对当前storefile的read对象,synchronized (metaIndex.blockKeys[block])

然后get,若无则加进map中。有则直接返回Block

 

但是这个逻辑在Region split和compact的时候会有问题。

 

假设A split B 和C,此时读取C,那么首先会去get,发现不存在,然后将C的blockpath putin map。

 

如果compact刚好在get和put之间结束,此时又有读请求过来,那么此时它们的storefilepath是一样的;

由于是getMetaBlock,因此它们的blocknum也一致,故blockpath也一致;

 

而且compact结束以后创建的是新的reader对象,与C原来的HalfStoreFileReader对象不一致。此时该读请求也会去get map,发现map中没有该blockpath,也会执行put操作。

 

这样对同一block会有两次put,会抛出RTE: Cached an already cached block 目前来看这个异常不会造成数据丢失,对读操作也无影响,主要发生在getMetaBlock的时候。

 

由于compact以后对于数据所在block的number进行了更新故在LoadBlock时几乎不发生

 

目前来看这个异常不会造成数据丢失,对读操作也无影响,主要发生在getMetaBlock的时候。
由于compact以后对于数据所在block的number进行了更新故在LoadBlock时几乎不发生


参考:http://www.it165.net/database/html/201211/3239.html

原创粉丝点击