一、现象描述
近期,生产云平台监控发生Docker应用重启次数过多事故报警,经观察发现某些Docker应用不定期地出现重启现象,已严重影响服务正常提供
生产应用重启的判断条件:健康检查连续3次检查不通过
生产健康检查间隔时间设置为:5s,也就是说如果应用对健康检查请求在15s内未返回结果,则云平台自动重启应用
二、重启现象分析
2.1、线程池泄漏问题
(1)、方法一:pstree命令分析
第一步:使用ssh命令远程登录应用服务器,然后输入pstree查看各进程下的线程数,初步定位出现的进程和是否发生线程泄漏,效果如下:
第二步:然后输入命令:pstree -a,具体定位问题进程所在的工程
(2)、方法二:jvisualvm监控
jvm参数增加jmx监控参数:
#JMX监控开启,端口号:280$SERVICE_PORT
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -Djava.rmi.server.hostname=应用所在机器IP -Dcom.sun.management.jmxremote.port=未使用的端口号 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
打开终端工具,输入:jvisualvm 命令回车进入窗口界面,然后在在《远程》菜单上右击后点击《添加远程主机》,然后设置端口点击《确定》即可
(3)、线程泄漏原因分析
问题一:线程池频繁创建导致线程泄漏
经代码分析得出消息监听器频繁创建线程池,导致的线程泄漏
因此,对init函数进行改造,防止线程池的频繁创建,改造代码如下:
private Object lock = new Object();public void init() {if (pool != null) {return;}//同步锁机制,瞬间高并发场景防护synchronized (lock) {if (pool != null) {return;}int availableProcessors = Runtime.getRuntime().availableProcessors() * 2 + 1;pool = Executors.newFixedThreadPool(availableProcessors, new ThreadFactory() {public Thread newThread(Runnable r) {Thread thread = new Thread(r);String threadName = "MakeUpListener_Thread_" + threadCount.incrementAndGet();thread.setName(threadName);return thread;}});....代码省略}
}
问题二:线程池大小设置不合理
分析:
(1)、线程池频繁创建
(2)、应用程序高并发异步处理业务时,线程池中的线程大小设置过大,导致线程数大量创建去处理业务,从而导致正常的http请求响应过慢;例如hg-ws联名账户子系统线程池大小设置为:20000,在业务量大的情况下经常出现重启现象
(3)、应用接收到大量客户请求并进行处理,前面请求未处理完成,后面有新的请求,从而导致后边请求一直处于排队状态,请求响应时间较慢
解决方案:
(1)、线程池合理创建,线程池大小设置在合理的区间内
(2)、用户发送请求响应,只需业务主流程是同步执行,非主流程可以异步执行,例如:用户下单支付完成后,向统计系统发送用户交易统计的行为可以异步执行,减少请求占用时间
2.2、Full GC期间,暂停所有应用线程执行,导致请求无响应
分析方法:gc日志分析
第一步:JVM参数配置GC日志收集和输出参数
JAVA_OPTS="$JAVA_OPTS -verbose:gc"JAVA_OPTS="$JAVA_OPTS -Xloggc:/apps/dbconfig/gc.log"JAVA_OPTS="$JAVA_OPTS -XX:NumberOfGCLogFiles=20"JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=30m"JAVA_OPTS="$JAVA_OPTS -XX:+UseGCLogFileRotation"JAVA_OPTS="$JAVA_OPTS -XX:+PrintVMOptions"JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails"JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCTimeStamps"JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC"JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDateStamps"JAVA_OPTS="$JAVA_OPTS -XX:+PrintTenuringDistribution"JAVA_OPTS="$JAVA_OPTS -XX:+PrintAdaptiveSizePolicy"JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCApplicationStoppedTime"JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCApplicationConcurrentTime"JAVA_OPTS="$JAVA_OPTS -XX:+PrintConcurrentLocks"JAVA_OPTS="$JAVA_OPTS -XX:+TraceClassUnloading"
第二步:分析gc日志,可以通过专门的分析工具GCViewer进行分析,也可以通过在https://gceasy.io/网址上进行分析(本人采用线上GC分析方式)
经过对生产子系统(jdk1.8环境)的gc日志分析,发现生产应用使用的老年代的回收算法(Full Collection)是:UseParallelGC并行处理期(使用多个线程同时进行垃圾回收,多核环境下充分利用CPU资源,减少回收时间,是Server模式下的默认回收器),但在回收期间暂停应用线程的执行;Full GC的大部分原因是:MetaSpace初始化空间不够
经上面总结得出:应用Full GC时间最长长达24s,而生产应用重启主要是因为Full GC时间过长导致
因此主要采取如下方法:
1)、优化JVM参数配置;例如:持久区大小、新老生代比例、回收算法等
2)、健康检查间隔时间延长
应用JVM参数新增:-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:NewRatio=1 -XX:SurvivorRatio=8