hbase的master和regionserver的jvm调优

先调大堆内存

默认的RegionServer的内存才1GB,而Memstore默认是占40%,所以分配给Memstore的才400MB,在实际场景下,很容易就写阻塞了。可以通过指定HBASE_HEAPSIZE参数来调整所有HBase实例(不管是Master还是RegionServer)占用的内存大小。
修改$HBASE_HOME/conf/hbase-env.sh为
export HBASE_HEAPSIZE=8G
这个参数会影响所有HBase实例,包括Master和Region。这样的话Master和RegionServer都会占用8GB,建议用Master和RegionServer专有的参数来分别设定他们的内存大小。

PermSize的调整
hbase-env.sh中
在这里插入图片描述
提示
这两句配置的意思是Master和RegionServer的永久对象区(Permanent Generation,这个区域在非堆内存里面)占用了128MB的内存。根据注释的意思是这个配置存在的意义是为了在JDK 7下可以安全运行实例,所以如果你用的是JDK 8可以删掉这两行,并且由于JDK 8已经去除了PermGen,所以设置了也没用。

分别调master和regionserver的堆内村内存在这里插入图片描述

这样就把Master的JVM内存设置为4GB,把RegionServer的内存设置为8GB了。
提示:永远至少留10%的内存给操作系统来进行必要的操作。

如何根据机器的内存大小设置合适的Master或者RegionServer的内存大小

举例:
现在有一台16GB的机器,上面有MapReduce服务、RegionServer和DataNode(这三位一般都是装在一起的),那么建议按照如下配置设置内存:
2GB:留给系统进程。
8GB:MapReduce服务。平均每1GB分配6个Map slots + 2个
Reduce slots。
4GB:HBase的RegionServer服务。
1GB:TaskTracker。
1GB:DataNode。
如果同时运行MapReduce的话,RegionServer将是除了MapReduce以外使用内存最大的服务。如果没有MapReduce的话,RegionServer可以调整到大概一半的服务器内存。

可怕的Full GC

JVM的堆内存越大,Full GC的时间越久。Full GC有时候可以达到好几分钟。在Full GC的时候JVM会停止响应任何的请求,整个JVM的世界就像是停止了一样,所以这种暂停又被叫做Stop-The-World(STW)。当ZooKeeper像往常一样通过心跳来检测RegionServer节点是否存活的时候,发现已经很久没有接收到来自RegionServer的回应,会直接把这个RegionServer标记为已经宕机。等到这台RegionServer终于结束了FullGC后,去查看ZooKeeper的时候会发现原来自己已经“被宕机”了,为了防止脑裂问题的发生,它会自己停止自己。这种场景称为RegionServer自杀,它还有另一个美丽的名字叫朱丽叶暂停,而且这问题还挺常见的,早期一直困扰着HBase开发人员。所以我们一定要设定好GC回收策略,避免长时间的Full GC发生,或者是尽量减小Full GC的时间

GC回收策略优化

JVM提供了4种GC回收器:
串行回收器(SerialGC)。
并行回收器(ParallelGC),主要针对年轻带进行优化(JDK 8默认策略)。
并发回收器(ConcMarkSweepGC,简称CMS),主要针对年老带进行优化。
G1GC回收器,主要针对大内存(32GB以上才叫大内存)进行优化。
组合方案如下

ParallelGC和CMS的组合方案

并行收回器的性能虽然没有串行回收器那么好,但是Full GC时间较短。对于RegionServer来说,Full GC是致命的,就算性能下降一些也没有关系,所以我们最好使用并行回收器。
并发回收器主要是减少老年代的暂停时间,可以保证应用不停止的情况下进行收集。但是它也有缺点,那就是每次都会留下一些“浮动垃圾”。这些浮动垃圾只能在下次垃圾回收的时候被回收,不过这些我们也可以忍受。基于以上描述比较符合HBase的配置是:
年轻带使用并行回收器ParallelGC。
年老带使用并发回收器ConcMarkSweepGC。
修改的方式还是修改$HBASE_HOME/conf/hbase-env.sh,在前面修改xms和xmx的地方加上
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
在这里插入图片描述

G1GC方案

引入G1GC策略的原因是,就算采用了CMS策略,我们还是不能避免Full GC。因为在以下两种情况
下,CMS还是会触发Full GC:
在CMS工作的时候,有一些对象要从年轻代移动到老年代,但是此时老年代空间不足了,此时只能触发FullGC,然后引发STW(Stop The World)暂停,JVM又开始不响应任何请求了。
当被回收掉的内存空间太碎太细小,导致新加入老年代的对象放不进去,只好触发Full GC来整理空间,JVM还是会进入不响应任何请求的状态。
G1GC策略通过把堆内存划分为多个Region,然后对各个Region单独进行GC,这样整体的Full GC以被最大限度地避免(Full GC还是不可避免的,我们只是尽力延迟Full GC的到来时间),而且这种策略还可以通过手动指定MaxGCPauseMillis参数来控制一旦发生Full GC的时候的最大暂停时间,避免时间太长造成RegionServer自杀。设置的方式是:
在这里插入图片描述

其他

如果你的RegionServer内存小于4GB,就不需要考虑G1GC策略了,直接用-XX:+UseParNewGC-XX:+UseConcMarkSweepGC。
如果你的RegionServer内存大于32GB,建议使用G1GC策略。
在这里插入图片描述
Justin Kestelyn在文章Tuning Java Garbage Collection forHBase中经过测试得出的调优参数结果,供参考:

  • 32GB heap的时候,-XX:G1NewSizePercent=3。
  • 64GB heap的时候,-XX:G1NewSizePercent=2。
  • 100GB或者更大的内存的时候,-XX:G1NewSizePercent=1。
    其他参数:
  • -XX:+UseG1GC。
  • -Xms100g -Xmx100g(文中做实验的堆内存大小)。
  • -XX:MaxGCPauseMillis=100。
  • -XX:+ParallelRefProcEnabled。
  • -XX:-ResizePLAB。
  • -XX:ParallelGCThreads= 8+(40-8)(5/8)=28。
  • -XX:G1NewSizePercent=1。

Memstore的专属JVM策略MSLAB

采用了CMS后还是发生Full GC的原因是:
(1)同步模式失败(concurrent mode failure):在CMS还没有把垃圾收集完的时候空间还没有完全释放,而这个时候如果新生代的对象过快地转化为老生代的对象时发现老生代的可用空间不够了。此时收集器会停止并发收集过程,转为单线程的STW(Stop The World)暂停,这就又回到了Full GC的过程了。不过这个过程可以通过设置-
XX:CMSInitiatingOccupancyFraction=N来缓解。N代表了当JVM启动垃
圾回收时的堆内存占用百分比。你设置的越小,JVM越早启动垃圾回收
进程,一般设置为70。
(2)由于碎片化造成的失败(Promotion Failure due toFragmentation):当前要从新生代提升到老年代的对象比老年代的所有可以使用的连续的内存空间都大。比如你当前的老年代里面有500MB的空间是可以用的,但是都是1KB大小的碎片空间,现在有一个2KB的对象要提升为老年代却发现没有一个空间可以插入。这时也会触发STW暂停,进行Full GC。
这个问题无论你把-XX:CMSInitiatingOccupancyFraction=N调多小都是无法解决的,因为CMS只做回收不做合并,所以只要你的RegionServer启动得够久一定会遇上Full GC。

为什么会出现碎片内存空间

Memstore是会定期刷写成为一个HFile的,在刷写的同时这个Memstore所占用的内存空间就会被标记为待回收,一旦被回收了,这部分内存就可以再次被使用,但是由于JVM分配对象都是按顺序分配
下去的,所以你的内存空间使用了一段时间后的情况如图 所示:
在这里插入图片描述

假设红色块占用的内存大小都是1KB,此时有一个2KB大小的对象从新生代升级到老生代,但是此时JVM已经找不到连续的2KB内存空间去放这个新对象了。
其实JVM为了避免这个问题有一个基于线程的解决方案,叫TLAB(Thread-Local allocation buffer)。当你使用TLAB的时候,每一个线程都会分配一个固定大小的内存空间,专门给这个线程使用,当线程用完这个空间后再新申请的空间还是这么大,这样下来就不会出现特别小的碎片空间,基本所有的对象都可以有地方放。缺点就是无论你的线程里面有没有对象都需要占用这么大的内存,其中有很大一部分空间是闲置的,内存空间利用率会降低。不过能避免Full GC,这些都是值得的。
但是HBase不能直接使用这个方案,因为在HBase中多个Region是被一个线程管理的,多个Memstore占用的空间还是无法合理地分开。于是HBase就自己实现了一套以Memstore为最小单元的内存管理机制,称为MSLAB(Memstore-Local Allocation Buffers)。这套机制完全沿袭了TLAB的实现思路,只不过内存空间是由Memstore来分配的。
MSLAB的具体实现如下:

  • 引入chunk的概念,所谓的chunk就是一块内存,大小默认为2MB。
  • RegionServer中维护着一个全局的MemStoreChunkPool实例,从名字很容看出,是一个chunk池。
    每个MemStore实例里面有一个MemStoreLAB实例。
  • 当MemStore接收到KeyValue数据的时候先从ChunkPool中申请一个chunk,然后放到这个chunk里面。
  • 如果这个chunk放满了,就新申请一个chunk。
  • 如果MemStore因为刷写而释放内存,则按chunk来清空内存。

跟MSLAB相关的参数是:

  • hbase.hregion.memstore.mslab.enabled:设置为true,即打开MSLAB,默认为true。
  • hbase.hregion.memstore.mslab.chunksize:每个chunk的大小,默认为2048 * 1024 即2MB。
  • hbase.hregion.memstore.mslab.max.allocation:能放入chunk的最大单元格大小,默认为256KB,已经很大了。
  • hbase.hregion.memstore.chunkpool.maxsize:在整个memstore可以占用的堆内存中,chunkPool占用的比例。该值为一个百分比,取值范围为0.0~1.0。默认值为0.0。
  • hbase.hregion.memstore.chunkpool.initialsize:在RegionServer启动的时候可以预分配一些空的chunk出来放到chunkPool里面待使用。该值就代表了预分配的chunk占总的chunkPool的比例。该值为一个百分比,取值范围为0.0~1.0,默认值为0.0。

版权声明:本文为Robots_原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。