生产Docker应用重启排查经历

一、现象描述

        近期,生产云平台监控发生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

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值