`
zhangxiong0301
  • 浏览: 351159 次
社区版块
存档分类
最新评论

hbase中的MSLAB

 
阅读更多

 

 

CDH用MSLAB避免Hbase FullGC

 

使用Hbase过程中如果出现长时间Stop-The-World GC,会造成以下两种影响:

 

1.     在Stop-The-World GC过程中,用户请求被阻塞,导致明显的延迟甚至超时。

2.     Hbase通过zookeeper维系集群节点存活。如果长时间没有向zookeeper发送ping消息,则master会启动任务让其他节点接替该节点。

 

 

JAVA GC

 

目前java主要采用分代垃圾回收策略。这种策略假设java虚拟机里的对象either die young, or stick around for quite a long time。不同生命期的对象采用各自最合适的垃圾回收算法可以提高回收效率。

年轻代采用ParNew GC:首先停止所有java工作线程,然后跟踪左右对象的引用确定对象是否存活,最后移动这些对象到另一个区域并更新所有引用指向到新的对象地址。parNew GC 的速度很快,因为年轻代的空间本来就很小,而且这是多线程并行回收。这种基于复制的垃圾回收再回收空间同时也把空间进行压缩,即空闲空间集中在一起。

老年代垃圾回收CMS:

老年代垃圾回收主要采用CMS,回收分几个阶段,有的是Stop-The-Word的,有的阶段可以和java工作线程并行执行。Stop-The-Word的两个阶段时间很短,并发阶段则可以和工作线程并行执行,因此整个过程不会对请求有太大延迟;另外,垃圾CMS不会对回收后的空间进行压缩,因此会产生内存碎片。具体步骤如下:

 

 ·  initial-mark (stops the world). In this phase, the CMS collector places a mark on the root objects. A root object is something directly referenced from a live Thread – for example, the local variables in use by that thread. This phase is short because the number of roots is very small.

 

·  concurrent-mark (concurrent). The collector now follows every pointer starting    from the root objects until it has marked all live objects in the system.

 

·  remark (stops the world). Since objects might have had references changed, and    new objects might have been created during concurrent-mark, we need to go back      and take those into account in this phase. This is short because a special data    structure allows us to only inspect those objects that were modified during the    prior phase.

 

·  concurrent-sweep (concurrent). Now, we proceed through all objects in the        heap. Any object without a mark is collected and considered free space. New        objects allocated during this time are marked as they are created so that they      aren’t accidentally collected.

 

 

CMS有两种失败的情况,这两种情况一旦发生,则CMS回收算法变成Stop-the-world的单线程复制垃圾回收,将所有数据拷贝到jvm堆空间的开始处。

 

1) Concurrent Mode Failure:即CMS在垃圾回收过程中还没来得及释放空间,源源不断的java对象被写入进老年代,导致老年代空间占满从而无法继续执行java工作线程。这种情况可以通过设置CMSInitiatingOccupancyFraction 参数解决。

 

2)Promotion Failure due to Fragmentation:CMS如期进行了垃圾回收,但是由于CMS不会对堆空间进行压缩,从而导致堆内存碎片;而且CMS执行次数越多,会是碎片更严重。最后的结果就是线程请求分配一个稍大的对象时,已经找不到一块满足大小条件的连续堆空间。这种情况基本无法避免,hbase在应用采用MSLAB主要就是解决CMS垃圾回收后的碎片问题。可以通过设置java启动参数-XX:PrintFLSStatistics=1,让java在垃圾回收时打印出垃圾回收时空闲空间,不连续的内存块数,以及不连续的内存块的最大块大小。

 

试验:估计堆碎片

 

设置了CMSInitiatingOccupancyFraction 为合理的值可以避免Concurrent Mode Failure,则HBASE中产生的长时间停顿基本确定是由于内存碎片引起的stop-the-world垃圾回收造成。首先确定什么情况下容易导致内存碎片,分别对以下三种场景进行试验测试:

 

 ·    Write-only: writes rows with 10 columns, each 100 bytes, across 100M distinct     row keys.

·  Read-only with cache churn: reads data randomly for 100M distinct row keys,      so that the data does not fit in the LRU cache.

·  Read-only without cache churn: reads data randomly for 10K distinct row        keys, so that the data fits entirely in the LRU cache

 

 

实验结果:

结果图中上半部分表示堆空间中的空闲大小,下半部分表示各个时刻堆内存中最大连续内存块的大小。从图可知,三种场景的内存特征是很不一样的。横坐标表示时间。

下面具体查看各种场景:

 

Write-only Workload

从图中可以看出,空闲内存从2.8G到3.8G之间波动。每次空闲内存减小到2.8G时,老年代堆内存使用达到initiating occupancy fraction 阈值,从而进行CMS垃圾回收,释放1G空间。测试期间空闲内存稳定在这个固定区间,说明没有内存泄露。max_chunk 的值一直减小到几乎接近零,直到没有办法进行内存分配,此时进行Stop-The-world垃圾回收,对内存进行进行压缩,空闲空间拼接在一起。

 

Read-only Workload with Cache Churn

带cache的读测试中,读取的记录数远比LRU block cache大,因此随着大量记录被放入堆中然后被释放掉可见很大的内存空闲空间波动。带缓存读的情况下垃圾回收比单纯写入场景时垃圾回收频繁的多,但是最大连续可用内存空间却相对稳定,几乎保持常量大小。

 

Read-only Workload without Cache Churn

 

不带缓存的读场景,由于每次只需要读入特定的RPC请求的记录,从而对象非常少,且不会进入老年代,因此老年代不存在垃圾回收。空闲空间和最大连续空间保持常量。

 

试验结论

 

1.      我们一直想消除的full GC是由堆内存碎片引起的,而不是concurrent-mode failure

2.      write-only 场景比两种读场景更容易导致内存碎片。

 

为什么写数据导致内存碎片?

 

Hbase将一张表自动分成很多分区,叫做region,表很大的时候一个节点可能维护很多region,每个region在内存里维护一个memStore,实际上是一个sorted Map。内存是有限的,当内存使用达到一个限制时,则会出发某些Region的memstore的flush。由于region很多,写入的数据是无序的,各个region的memStore中的数据在内存中混在一起,当一个region去flush时,就会形成内存的碎片,flush次数越多,碎片化就更严重,最终就需要stop-the-world垃圾回收整理碎片啦。假如一个节点有五个region(用不同的颜色表示),则一个region flush 后结果如下图:

 

 

MSLAB

 

如上描述,一个regionserver的内存里各个region的数据混合在一起,当某个region被flush到磁盘时,就会形成很多堆碎片。其实这跟java中gc模型的假设是冲突的:同一时间创建的对象,会在同一时间消亡。这个问题可以通过arena allocation来解决—每次分配内存时都是在一个更大的叫做arena的内存区域分配。一个典型的实现是TLAB,即每个线程维护自己的arena,每个线程用到的对象都在自己的arena区域分配。其实,jvm早已经实现了TLAB,但是这个对于hbase不适用,因为hbase的regionserver使用一个单独的线程来处理所有region的请求,就算这个线程用arena方式分配还是会把所有region的数据混在一起。因此hbase自己实现了MSLAB,即每个region的memStore自己实现了arena,使各个region的数据分开,就不会形成太细的碎片。Arena里存放的是KeyValue对象,这个对象,这些KeyValue对象时一样大的,不会导致严重碎片,相反这些KeyValue对象引用的字节数组才是引起碎片的主因。因此要做的就是把这些字节数组分配在一起。

 

MSLAB的具体实现:

 

 

      1) Each MemStore instance has an associated instance of a new class MemStoreLAB.

 

       2)MemStoreLAB retains a structure called curChunk which consists of a 2MB byte[] and                                 a nextFreeOffset pointer starting at 0.

 

       3)When a KeyValue is about to be inserted into the MemStore, it is first copied into curChunk and                 the nextFreeOffsetpointer is bumped by the length of the new data.

 

      4) Should the 2MB chunk fill up, a new one is allocated from the JVM using the usual method: new                 byte[2*1024*1024].

 

使用MSLAB的试验结果:从图可知,随着数据写入时间变长,最大连续内存块趋于稳定,而不像之前减小到接近0并触发Stop-The-world gc。

配置使用MSLAB

Configuration

Description

hbase.hregion.memstore.mslab.enabled

Set to true to enable this feature

hbase.hregion.memstore.mslab.chunksize

The size of the chunks allocated by MSLAB, in bytes (default 2MB)

hbase.hregion.memstore.mslab.max.allocation

The maximum size byte array that should come from the MSLAB, in bytes (default 256KB)

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics