同志们,此部分,重要的不能再重要了
1、HBase发展到当下,对其进行的各种优化从未停止,而GC优化更是其中的重中之重。
hbase gc调优方向
从0.94版本提出MemStoreLAB策略、Memstore Chuck Pool策略对写缓存Memstore进行优化开始,到0.96版本提出BucketCache以及堆外内存方案对读缓存BlockCache进行优化,再到后续2.0版本宣称会引入更多堆外内存,可见HBase会将堆外内存的使用作为优化GC的一个战略方向。
然而无论引入多少堆外内存,都无法避免读写全路径使用JVM内存,就拿BucketCache中offheap模式来讲,即使HBase数据块是缓存在堆外内存的,但是在读取的时候还是会首先将堆外内存中的block加载到JVM内存中,再返回给用户。
这句话的理解在:http://hbasefly.com/2016/04/26/hbase-blockcache-2///提到了
BucketCache工作模式 //
比如,内存分配时heap模式需要首先从操作系统分配内存再拷贝到JVM heap,相比offheap直接从操作系统分配内存更耗时;但是反过来,读取缓存时heap模式可以从JVM heap中直接读取,而offheap模式则需要首先从操作系统拷贝到JVM heap再读取,显得后者更费时。
可见,无论使用多少堆外内存,对JVM内存的使用终究是绕不过去,既然绕不过去,就还是需要落脚于GC本身,对GC本身进行优化。
回顾CMS GC 算法的工作原理
先上图:
文中对JVM的内存结构以及CMS GC进行了相当详细的介绍。
- 整个JVM内存由Young区、Tenured区和Perm区三部分组成,其中Young区又分为一个Eden区和两个Survivor区
- 整个对象生命周期简要说明(一定要烂熟于心,下文会一直用到):
(1)Young区:一个对象初始化之后,首先会进入Eden区,当Eden区满之后会触发一次Minor GC,Minor GC会检查Eden区所有对象是否依旧存活(是否有其他对象引用),如果存活,会将其从Eden区拷贝到Survivor区,并将这些存活对象的age加一,而死亡的对象会被作为垃圾回收。此时Eden区又空闲出来,等新对象填充,填充满之后再会触发Minor GC,如此往复。需要注意的是,每执行一次Minor GC,存活对象的age就会加一。
(2)Tenured区:一旦存活对象的age超多一定阈值就会晋升到Tenured区,因此可以理解为Tenured区一般存放长寿对象。很显然,随着时间流逝,Tenured区也会被填充满,此时就会触发CMS GC(old gc),这种GC相对比较复杂,由6个步骤组成分别为(初始标记;并发标记;并发预清理;重新标记;并发清理;并发重置。),详见参考文章。 - 无论是Minor GC还是CMS GC,都会’Stop-The-World’,即停止用户的一切线程,只留下gc线程回收垃圾对象。其中Minor GC的STW时间主要耗费在复制阶段(年轻代本身就是copy算法),CMS GC的STW时间主要耗费在标示垃圾对象阶段(也就是并发标记和并发预清理阶段)。
先来看看GC调优的最终目标和基本原则:
- 平均Minor GC时间尽可能短。因为整个Minor GC都处于STW(STW时间主要耗费在复制阶段),因此短时间Minor GC会使用户读写更加平稳,延迟可控。
- CMS GC次数越少越好。时间越短越好。一方面是因为一次CMS GC一般都会引起至少秒级的应用暂停,对用户读写影响较大;另一方面频繁的CMS GC会产生大量的内存碎片,严重的时候会引起Full GC,导致RegionServer宕机。
下面对参数的调优技巧都谨遵以上原则,尤其对于HBase这类延迟敏感性项目而言,在尽量避免严重影响用户读写的情况下使得GC更加平稳、暂停时间更短!
//也就是遵循一个原则 不管是minor gc还是cms gc尽量时间段,减少cms gc的次数。
CMS GC优化技巧(三个阶段 )
主要分三个阶段进行。
1、第一阶段会介绍适用于所有场景下的GC参数配置,这些参数不需要太多解释读者就可以轻松理解;
2、第二阶段和第三阶段分别就两组参数进行调优讲解,这两组参数一般会根据不同的应用场景进行设置才能使得GC效果最好,鉴于这两组参数的复杂性,
阶段一:默认推荐配置 //每个参数什么意思讲解
-Xmx -Xms -Xmn -Xss -XX:MaxPermSize= M -XX:SurvivorRatio=S -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:MaxTenuringThreshold=N -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=C -XX:-DisableExplicitGC
-Xmx:分配给JVM的最大堆内存
-Xms:分配给JVM的初始内存,此值一般和Xmx设置相同
-Xmn:分配给young区内存大小,此值的设置对系统性能影响很大,后面第二阶段将会重点讨论此值的参数的调优 -Xss:分配给每个线程的堆栈大小,在一些对线程数敏感的系统中该值设置比较重要,一般设置为256K~1M左右
-XX:MaxPermSize= M :分配给持久代的内存大小
-XX:SurvivorRatio=S : 表示young区中eden区和survivor区的内存大小比例,默认为8 该值的设置对系统性能影响很大,第三阶段会重点讨论该参数的调优
-XX:+UseConcMarkSweepGC :表示回收器使用CMS CG策略
-XX:+UseParNewGC :表示young区采用并行回收机制 推荐使用 &&&&&
-XX:+CMSParallelRemarkEnabled : 表示cms的remark阶段采用并行的方式,推荐使用 &&&&&
-XX:MaxTenuringThreshold=N : 表示young区对象晋升到Tenured区的阈值,该值的设置对系统的影响很大,在第三节点会重点讨论*****
-XX:+UseCMSCompactAtFullCollection :表示每次执行完cms gc之后执行一次碎片整合 推荐使用 &&&&&
-XX:+UseCMSInitiatingOccupancyOnly :表示cms gc只基于参数CMSInitiatingOccupancyFraction触发
-Xx:CMSInitiatingOccupancyFraction : 表示当tenured(老年代)区内存使用量超过tenured总大小的百分比超过该阈值之后会触发cms gc,该值一般设置为70%~80%
-XX:-DisableExplicitGC : 表示禁止使用命令System.gc() 该命令用于触发整个JVM的垃圾回收,一般都是长时间full gc 推荐使用 &&&&&
-XX:+PrintTenuringDistribution才能打印对应日志,强烈建议线上集群开启该参数,&&&&&
使用所有场景下的gc参数调优
*****通过上文对各个GC参数的说明,可以轻松得出第一阶段推荐的参数设置如下,这样的设置基本适用于所有的场景:
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=75% -XX:-DisableExplicitGC
调优预准备
1、上文通过解释各个GC参数意义给出了基本的推荐设置,同时也提到几个对性能影响重大的参数:
2、Xmn、SurvivorRatio以及MaxTenuringThreshold,下面会通过理论推理+实验验证的方式对这几个参数在HBase系统的设置进行调优。
需要强调的是HBase全部配置为BucketCache模式,而不是LruBlockCache。使用了大量堆外内存作为读缓存,在很大程度上优化了GC
可见BucketCache模式比LruBlockCache模式GC表现好很多,强烈建议线上配置BucketCache模式。
GC日志分析
介绍完实验基本条件后,再对GC日志进行简单的解释,方便下文对日志进行分析。需要注意只有在添加参数-XX:+PrintTenuringDistribution才能打印对应日志,强烈建议线上集群开启该参数,
HBase场景内存分析
因此可以看出,HBase系统属于长寿对象居多的工程,因此GC的时候只需要将RPC这类短寿对象在Young区淘汰掉就可以达到最好的GC效果。
阶段二:NewParSize调优 // 也就是 -Xmn 参数 //细节看博客内存
NewParSize表示young区大小,而young区大小直接决定minor gc的频率。
minor gc频率一方面决定单次minor gc的时间长短,gc越频繁,gc时间就越短;一方面决定对象晋升到老年代的量,gc越频繁,晋升到老年代的对象量就越大。解释起来就是:
- 增大young区大小,minor gc频率降低,单次gc时间会较长(young区设置更大,一次gc就需要复制更多对象,耗时必然比较长),业务读写操作延迟抖动较大。反之,业务读写操作延迟抖动较小,比较平稳。
- 减小young区大小,minor gc频率增快,但会加快晋升到老年代的对象总量(每gc一次,对象age就会加一,当age超过阈值就会晋升到老年代,因此gc频率越高,age就增加越快),潜在增加old gc风险。
因此设置NewParSize需要进行一定的平衡,不能设置太大,也不能设置太小。
小结:
1、Xmn=2是一个最优的选择
//Xmn设置过小会导致CMS GC性能较差,而设置过大会导致Minor GC性能较差,
//因此建议在JVM Heap为64g以上的情况下设置Xmn在1~3g之间
//在32g之下设置为512m~1g
细节:
具体最好经过简单的线上调试;需要特别强调的是,笔者在很多场合都看到很多HBase线上集群会把Xmn设置的很大,比如有些集群Xmx为48g,Xmn为10g,查看日志发现GC性能极差:单次Minor GC基本都在300ms~500ms之间,CMS GC更是很多超过1s。在此强烈建议,将Xmn调大对GC(无论Minor GC还是CMS GC)没有任何好处,不要设置太大。
阶段三:增大Survivor区大小(减小SurvivorRatio) & 增大MaxTenuringThreshold
1、-XX:SurvivorRatio=S : 表示young区中eden区和survivor区的内存大小比例,默认为8 该值的设置对系统性能影响很大,第三阶段会重点讨论该参数的调优 2、-XX:MaxTenuringThreshold=N : 表示young区对象晋升到Tenured区的阈值,该值的设置对系统的影响很大,在第三节点会重点讨论
小结:
1、一般情况下,默认MaxTenuringThreshold=15已经相对比较大,不需要做任何调整。
2、对于Minor GC来说,SurvivorRatio设置对其影响不是很大。而对于CMS GC来说,将SurvivorRatio设置过大简直就是灾难,性能极其差。而和默认值SurvivorRatio=8相比,将SurvivorRatio调小有利于短寿小对象更充分地淘汰,因此建议将SurvivorRatio=2
CMS调优结论
- 缓存模式采用BucketCache策略Offheap模式
- 对于大内存(大于64G),采用如下配置:
-Xmx64g -Xms64g -Xmn2g -Xss256k -XX:MaxPermSize=256m -XX:SurvivorRatio=2 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled -XX:MaxTenuringThreshold=15 -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=75 -XX:-DisableExplicitGC
总结:
1、其中Xmn可以随着Java分配堆内存增大而适度增大,但是不能大于4g,取值范围在1~3g范围;
2、SurvivorRatio一般建议选择为2;
3、MaxTenuringThreshold设置为15;
4、对于小内存(小于64G),只需要将上述配置中Xmn改为512m-1g即可
最后小结:
通用场景
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=75% -XX:-DisableExplicitGC -XX:+PrintTenuringDistribution
cms gc调优(小于64G内存的)
-Xmx""g -Xms""g -Xmn1g -Xss256k -XX:MaxPermSize=256m -XX:SurvivorRatio=2 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled -XX:MaxTenuringThreshold=15 -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=75 -XX:-DisableExplicitGC -XX:+PrintTenuringDistribution
cms gc对于大内存(大于64G),采用如下配置:
-Xmx64g -Xms64g -Xmn2g -Xss256k -XX:MaxPermSize=256m -XX:SurvivorRatio=2 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled -XX:MaxTenuringThreshold=15 -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=75 -XX:-DisableExplicitGC -XX:+PrintTenuringDistribution
小伙伴的问题1: 关于gc日志的问题
博主您好,想请教一下,gc的日志,/var/log/hbase/gc.regionserver.log每次重启就会覆盖,能够配置成追加写吗。有时hbase报错,想去查gc的问题时,一旦重启就没了之前的gc日志信息了。
答:
可以看看你的jvm配置 将GC日志设为3或者更多 -Xloggc:$HBASE_LOG_DIR/gc-regionserver-date +%Y%m%d-%H-%M
.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=3
小伙伴的问题2:
博主你好,我也是做了好多测试,发现不同读写比情况下LRU的性能(吞吐、延时)都要强于CBC,看到文章中也提到了这一点,但还是想不懂为什么CBC要差一些,所以很想请教博主?
答:
因为CBC模式下bucketcache(offheap模式)使用堆外内存,堆外内存读取会比jvm内存读取复杂,流程更多,所以在全内存场景喜爱LRU完全好于CBC,在缓存基本不命中场景下两者吞吐量延迟基本相当
全内存场景是指?
数据量比较小或者有大量热点读的场景 大多数读都落在BlockCache场景
小伙伴的问题3:
范大神,能问下你们hbase集群zookeeper.session.timeout这个参数设置多大么,能否通过调大这个参数来降低因为GC导致的RS连接zk超时挂掉问题呢? 假如我调整到比如180秒会对hbase造成什么影响吗? 万分感谢!!
答:
离线集群的话设置大点没啥问题 实时在线对延迟敏感的集群就不能设置太大
小伙伴的问题:
博主方便用 jmap -heap PID 显示出博主的配置,然后解释这些参数吗?这样可能更加方便大家看学习博主的配置。
我这边的答案为
[root@hdfs-master-80-121 hbase]# ps -ef|grep hbase
hbase 3004 2490 0 Mar06 ? 00:54:30 /usr/java/jdk1.8.0_102/bin/java -Dproc_regionserver -XX:OnOutOfMemoryError=kill -9 %p -Djava.net.preferIPv4Stack=true -Xms4294967296 -Xmx4294967296 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/hbase_hbase-REGIONSERVER-e72e026ce56e3850d7702a2ca6ecc206_pid3004.hprof -XX:OnOutOfMemoryError=/usr/lib64/cmf/service/common/killparent.sh -Dhbase.log.dir=/var/log/hbase -Dhbase.log.file=hbase-cmf-hbase-REGIONSERVER-hdfs-master-80-121.log.out -Dhbase.home.dir=/opt/cloudera/parcels/CDH-5.9.2-1.cdh5.9.2.p0.3/lib/hbase -Dhbase.id.str= -Dhbase.root.logger=INFO,RFA -Djava.library.path=/opt/cloudera/parcels/CDH-5.9.2-1.cdh5.9.2.p0.3/lib/hadoop/lib/native:/opt/cloudera/parcels/CDH-5.9.2-1.cdh5.9.2.p0.3/lib/hbase/lib/native/Linux-amd64-64 -Dhbase.security.logger=INFO,RFAS org.apache.hadoop.hbase.regionserver.HRegionServer start
hbase 3090 3004 0 Mar06 ? 00:26:57 /bin/bash /usr/lib64/cmf/service/hbase/hbase.sh regionserver start
hbase 6981 3090 0 18:04 ? 00:00:00 sleep 1
root 6983 21889 0 18:04 pts/0 00:00:00 grep --color=auto hbase[root@hdfs-master-80-121 hbase]# jmap -heap 3004
Attaching to process ID 3004, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.102-b14using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GCHeap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 4294967296 (4096.0MB)
NewSize = 348913664 (332.75MB)
MaxNewSize = 348913664 (332.75MB)
OldSize = 3946053632 (3763.25MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 314048512 (299.5MB)
used = 94743000 (90.35396575927734MB)
free = 219305512 (209.14603424072266MB)
30.168269034817143% used
Eden Space:
capacity = 279183360 (266.25MB)
used = 91102232 (86.8818588256836MB)
free = 188081128 (179.3681411743164MB)
32.63168406598445% used
From Space:
capacity = 34865152 (33.25MB)
used = 3640768 (3.47210693359375MB)
free = 31224384 (29.77789306640625MB)
10.442426867951127% used
To Space:
capacity = 34865152 (33.25MB)
used = 0 (0.0MB)
free = 34865152 (33.25MB)
0.0% used
concurrent mark-sweep generation:
capacity = 3946053632 (3763.25MB)
used = 16010552 (15.268852233886719MB)
free = 3930043080 (3747.9811477661133MB)
0.4057357931013544% used14606 interned Strings occupying 1393920 bytes.
小伙伴的问题6:
您好,请问您为什么没有使用G1 GC机制呢?
答:
没有用G1GC一方面是因为我们这边内存使用量没有那么大,另一方面G1GC要用好需要关注太多参数配置。不过以后是一个大的方向
转载于:https://blog.51cto.com/12445535/2373230