GC trouble on HBase

最近几天一直纠结在HBase的GC问题上,虽然没有质的收获,但也有一些新的心得,对HBase上GC产生的过程有了更深的认识。

由于基本采用的都是CMS回收,所以讨论也针对的是CMS回收。

GC问题可以简单地归结为YGC停顿过长和FullGC触发。

1.FullGC触发
CMS下触发FullGC的原因:
a.Concurrent mode failure,old区要分配内存了,但是old区空间不够,而此时CMS正在进行中。
解决方法:降低YGC频率,降低CMS gc触发时机(降低CMSInitiatingOccupancyFraction的值)

b.Promotion Failed,old区要分配内存了,但是找不到空间分配,却还没达到CMS的触发值,这个问题主要是由heap碎片+YGC晋升对象过大导致,HBase中YGC期间晋升的大对象为:LRUBlockCache中的Block(64KB)和开启MSLab后的Chunk(2MB,Chunk介绍http://www.taobaotest.com/blogs/2310),这两个问题可以通过BucketCache(HBASE-7404)和ChunkPool(HBASE-8163)解决之


2.YGC停顿过长
YGC时间构成=扫描stack+扫描card table+扫描root+对象拷贝,
扫描stack是扫描线程栈找出活跃对象的时间,一般比较快;

Hotspot将old区按512字节的page进行划分,存放到内部的card表中,所以扫描card table的时间,取决于old区的大小;

当修改old区的对象的某一个引用后,那么就会标记这个对象所在的page为脏页,并且将这个page中的所有对象作为root,进行扫描,所以如果频繁地更改old区对象中的引用,那么就会有很多root需要扫描,这部分时间基本决定了YGC的时间;

举个HBase中的例子,Memstore中有个排序Set用来存储刚刚写进来的keyvalue,当flush后,这个Set对象及其中的KeyValue对象就会死亡,由于flush周期较长,所以很多对象会在old区存活一定的时间。每当向Memstore写入一条数据就会向Set中插入一条KeyValue,这就意味着我们会修改这个新插入的Keyvalue对应的Node Entry的前继对象的next指针的引用


YGC过程中,所有活着的对象会拷贝到to space或者old区,这些活着对象的大小决定了拷贝过程的时间


按照对象的存活时间我们可以将HBase中的对象分为三类:1.函数内的临时对象;2.存活一定周期的对象(Memstore中的KeyValue,LRUBlockCache中的Block,开启WAL压缩后的row/table/region/cf对象);3.永久存活的对象(开启BucketCache后的Block,开启ChunkPool后的Chunk);

在开启BucketCache和ChunkPool,YGC后晋升的数据会十分的少,拷贝对象的时间可以忽略不计

于是,在一个YGC周期中,新产生的类型2对象的数目会直接决定了YGC的时间,相比而言,KeyValue比Block的数目多很多,这就是说 一个YGC周期写入的KeyValue数目决定着YGC的时间

关于YGC的RT:
1.HBase的写是造成YGC RT过长的主因
2.在BucketCache后,HBase的读对YGC RT的影响较小


关于YGC的频率,有这么2个发现:
1.compaction期间,YGC频率大大加快
2.单条put比批量put,YGC频率要快很多,通过观察,单条put的方式,1G young区,4000TPS,大概2秒一次的YGC,而批量方式,则YGC周期要长很多,单次YGC的时间也会长很多


关于UseAdaptiveSizePolicy的配置
配了后直接abort!!
为了控制YGC的时间,我们可以降低young区的大小,但是compaction期间,YGC频率会大大加快,所以想到启用UseAdaptiveSizePolicy,但是实践后发现会 直接导致Server Abort,原因是由于在调整young区的eden s0 s1的比例后HeapMax会不一致,导致内部报错


关于SurvivorRatio的配置
通过gcutil观察每次ygc后to space的所占比例,如果很低,那么可以加大这个配置值,在开启bucketcache和chunkpool后,这个配置值可以配的比较大。如果发现YGC后to space的比例一直是100%,那说明to space太小了,直接导致很多对象进入了old区

关于young区的大小
目前我们配置的young大小由4G,2G,1G,属于比较大的;在开启了BucketCache和ChunkPool后,晋升数据很少;对于RT敏感的应用,可以根据YGC的频率, 减少young区到512M甚至256M,来减少RT


YGC优化:
1.减小-XX:MaxTenuringThreshold,默认值为15,通常意味着一个对象经过15次YGC后还活着则会晋升到old区(当然还会有一些额外的因素,来影响晋升的速度),对于HBase中的对象,1.函数中的临时对象会很快淘汰;2.永久对象会一直在old区;3.周期存活的对象一般会晋升到old区后再死亡,所以我们可以减小这个值,一来可以减少对象拷贝,二来可以减少young区的对象数据,减少ygc期间的扫描

效果:1G young,50/100 KeyValue, MaxTenuringThreshold=15 -> MaxTenuringThreshold=3, YGC时间 0.034ms->0.014ms, 频率不变

2.终极改进:基于字节流的排序Set,向set中插入数据的时候,不再需要改变引用关系,而只需要改变byte值,这样可以完美的解决YGC问题,TODO。。。

猜你喜欢

转载自zjushch.iteye.com/blog/1839754