肝了两周,线上YGC频繁事故分析
高并发场景下很多不确定因素,造成线上机器YGC性能问题。gc耗时长/频繁的原因增加stop-the-world,导致系统可用率降低,影响上下游功能,甚至宕机。
项目A为提单、商品等提供基础服务, 线上一直平稳。 12.4日,下午4点收到线上大量报警信息。
通过线上监控可以看出, 几秒内最高发生8次ygc,如此频繁的gc造成为上游提供的接口大量超时,线上接口大面积处于瘫痪状态。
首先检查线上JVM参数,通过命令jinfo pid查看jvm配置,-Xmx3072M -XX:NewRatio=2 堆内存3g,年轻代大小为1g。机器4c8g。
-Xms3072M -Xmx3072M -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=8 -XX:NewRatio=2 -XX:SurvivorRatio=8 -server -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSFullGCsBeforeCompaction=2 -XX:+UseCMSCompactAtFullCollection -XX:+CMSScavengeBeforeRemark -server -XX:ParallelGCThreads=4 -XX:ConcGCThreads=4
jvm参数并没有什么不妥,接下来保留线上一台机器,通过 jmap -dump format=b,file=a 命令dump二进制文件,使用内存分析工具MAT 查看当前堆栈信息状况。
-
sun.misc.Launcher$AppClassLoader :类加载器;
-
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory:工厂类
-
org.apache.commons.pool2.impl.GenericKeyedObjectPool:线程池用于调度任务
这里发现可疑的地方,GenericKeyedObjectPool 线程池调度缓存连接,线程连接数却有128个,项目A集群有30节点,Redis总连接数128*30=3840个。这意味着线上有大量的线程访问Redis。
带着这个问题,检查线上线程监控,发现4点22分开始波动,比正常增加150个线程。通过观察ygc监控图和线程数监控图发现 ygc监控折线和线程波动是吻合的。
此外,虚拟机调度700多个线程,cpu负载过高的问题也会随之而来。
果不其然,紧接着收到cpu利用率报警,大量线程上下文切换,线上cpu使用百分之60以上。
沿着上面的思路猜测,增加的线程是Redis客户端线程连接,现在Redis客服端一般都是用Jedis, 内部使用线程池管理(不做详细介绍)。那么为什么创建上百个线程呢?
通过查看Redis集群监控,猜测的思路被一步步验证。客户端连接数增加了2000--4000个。
上面已经提到过,项目A提供基础的服务,其他工程通过rpc调用获取基本信息。经过一系列内部逻辑的排查和外部api方法检测。其中的一个方法在4点22分开始调用量飙升,达到400k/min的频率。
梳理该方法发现, 这是一个批量接口,提供基础配置信息,数据缓存在Redis中。当前接口调用方在未通知的情况下修改了调用方式。原本是以店纬度获取基本信息, 现在是以店下的品纬度获取基本信息。店下的品有成千上百个, 修改调用方式后调用量就是增加上百倍。
至此问题已经找到,为什么会创建如此多的Redis线程呢, 隐约感觉到Redis客户端线程池有问题。
经过一番源码的排查,配置类默认配置:核心线程数量默是128,最大线程是2的32次方-1,队列SynchronousQueue。
修改Redis客户端线程池配置,通知上游修改调用方式后,线上回归正常。
合理的运用线程池,看似一行不起眼的代码, 在线上场景中却暴露致命的问题。
遇到线上问题时,要冷静分析因果关系,早先留下坑迟迟没有爆出的问题,在经历版本不断迭代的过程中会暴露出来。要学会利用分析工具, 提升线上实操的能力。
推荐好文
强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!
原文始发于微信公众号(Java笔记虾):肝了两周,线上YGC频繁事故分析