4. 一次有趣的 JVM 性能调优
4.1 问题描述
-
服务qps 2000左右,性能稳定。但每天都会偶尔出现几次查询Db超时
-
超时机制:客户端使用并发异步查询,设置超时时间为200ms
ByteBuffer byteBuffer = completionStage.toCompletableFuture().get(getTimeout, TimeUnit.MILLISECONDS);
-
4.2 问题定位
-
怀疑Db服务端响应慢,分析过程如下
-
监控:Db服务端没有超过200ms的查询 —— 排除服务端慢查询的case
- 分析:服务端没有慢查询,客户端有超时,会不会是因为网络问题(网络IO)
-
监控:网络IO持续问题 —— 因为超时是偶先,网络IO很稳定的情况下不太可能是此原因
- 问题定位到客户端和应用本身
-
-
怀疑Db客户端有问题,分析过程如下
- 场景描述:qps较高,Db客户端采用异步执行
- Db客户端异步原理简述:客户端会为每个服务端节点的每个CPU线程创建一个连接
- 怀疑点:查询db任务较多,任务进入客户端线程池的队列中,等待执行时间超过200ms,导致超时
- 分析:现在每个客户端会建立192个连接,且服务端处理单次任务大概在毫秒级别,所以基本不太可能是这个原因
- 连接如图
- 怀疑点:查询db任务较多,任务进入客户端线程池的队列中,等待执行时间超过200ms,导致超时
-
查看服务本身各项监控指标(此时基本快没有头绪了!!)
-
服务的网络、cpu、磁盘、内存一切都很恒定
-
内存:Minnor GC正常,基本未产生Full GC
- 单独列内存监控截图,是因为一般服务性能下降,是因为内存泄漏导致Full GC、内存溢出等原因
-
-
可以看到内存在38%左右就降下来,38%是因为配置的Minnor的大小占内存30%
- 此处可以看到,虽然Minnor GC基本不影响性能,但是明细可以感觉到gc的频率很高
- 开始怀疑是否由于频繁的Minnor GC导致
-
查看服务GC具体情况;得出结论如下:
-
优化前:Minnor GC频率基本20分钟一次,超过100ms的gc频现(年轻代大小3G)
-
优化前:Minnor GC平均GC耗时:85ms
-
-
查看jvm参数配置发现:年轻代使用UseParNewGC收集器,老年代使用CMS(具体不描述)
- UseParNewGC:并发串行收集器,它是工作在新生代的垃圾收集器,它只是将串行收集器多线程化,除了这个并没有太多创新之处,而且它们共用了相当多的代码。它与串行收集器一样,也是独占式收集器,在收集过程中,应用程序会全部暂停。但它却是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作
-
此处怀疑点
- Minnor GC超过100ms(甚至300ms)的case,肯定会早成200ms超时限制
-
开始优化jvm参数(使用jdk 1.8默认收集器)
- 使用UseParallelGC收集器
- UseParallelGC:并行收集器,同时运行在多个cpu之间,物理上的并行收集器,跟上面的收集器一样也是独占式的,但是它最大化的提高程序吞吐量,同时缩短程序停顿时间,另外它不能与CMS收集器配合工作。
- 调整参数,设置最长gc耗时80ms,启动AdaptiveSizePolicy
- AdaptiveSizePolicy,会根据GC的情况自动计算计算 Eden、From 和 To 区的大小;
- 注意事项:
- 在 JDK 1.8 中,如果使用 CMS,无论 UseAdaptiveSizePolicy 如何设置,都会将 UseAdaptiveSizePolicy 设置为 false;不过不同版本的JDK存在差异;
- UseAdaptiveSizePolicy不要和SurvivorRatio参数显示设置搭配使用,一起使用会导致参数失效;
- 由于AdaptiveSizePolicy会动态调整 Eden、Survivor 的大小,有些情况存在Survivor 被自动调为很小,比如十几MB甚至几MB的可能,这个时候YGC回收掉 Eden区后,还存活的对象进入Survivor 装不下,就会直接晋升到老年代,导致老年代占用空间逐渐增加,从而触发FULL GC,如果一次FULL GC的耗时很长(比如到达几百毫秒),那么在要求高响应的系统就是不可取的。
- 使用UseParallelGC收集器
-
调优后效果
-
Minnor GC频率10分钟一次(年轻代大小2G)
-
平均GC耗时:25ms(优化前85ms)
-
最大GC耗时:30ms(优化前由300ms的case)
-
-
超时结果对比(武汉机房为jvm调优后,北京机房为调优前)
- 武汉与北京机房超时次数比例(三天数据):1:20
-
仍会出现超时情况,最终打算如下优化:
- 所有机房使用调优后的jvm参数,更换gc收集器
- 需要明白:200ms超时在高qps等情况下,难免会因为各种服务或网络问题偶现,不能完全避难
- 经验:我们之前是保持9999在1s内,999在800ms内(直接面对端上)
- 现状:我们期望的耗时 < 200ms
- 假设9999的标准是200ms,按照2000的qps计算,1秒内>0.2次超时即预警,5秒内>1次超时及预警
- 假设999的标准是200ms,按照2000的qps计算,1秒内>2次超时即预警
- 目前设置10分钟大于1次即预警,标准过高,预警意义不是很大
- 调整:venus小预警时间维度1分钟
- 1分钟内超时 > 1次超时(会打印2 次error)。(目前超时一次会打印两条error,待优化)