Redian新闻
>
抽丝剥茧:线上突发多次Full GC,终于解决了.....

抽丝剥茧:线上突发多次Full GC,终于解决了.....

公众号新闻

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 项目地址:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn
来源:juejin.cn/post/
7169543360632324126

今天分享一个项目jvm多次fgc的整个排查流程

上班后不久运维突然通知我们组,有一个应用在短时间内多次fgc,即将处于挂掉的状态。我登录skywalking,观察应用整体状况

交代下背景,该项目属于一个整合消息的项目,可以称之为消息中心,负责我们应用中的所有的推送,短信及公众号推送,消息都是通过业务方面投递到kakfa消费的方式,所以feign或者http调用的外部调用几乎没有

首先通过cpm(call per minute/每分钟服务被调用数)观察服务整体情况

我们可以看到在8.到8.07分这段时间有一个波峰,询问产品得知是有医院开启了抢购药品的业务(某些专科医院比如说皮肤病医院,会在一些特定时段,每天开启限制数量的药品抢购,这个抢购属于比较火爆的业务),所以这个流量峰值是正常的。流量一大,消息队列中的消息数量立刻就上来了:

那么我第一个想法,有没有可能是kafka消息堆积呢?应用的处理能力跟不上,导致消息大量堆积,带着这个问题,我去看一下skywalking监控的消费速度,打开Endpoint监控,观察slow endpoints:

可以看到最慢的kafka消费速率在234ms,对于一个需要调用第三方外部接口完成业务的消费来说,这个速度不可以说慢,甚至还可以说有点小快。没关系,只要是性能问题,那么总是有迹可循。我点开最高负载的服务实例查看gc次数:

可以看到在8.6分流量最高的这段时间fgc的次数达到了70次,这确实离谱,按照我的经验一个健康的应用甚至不应该一天fgc超过10次,这一分钟超过70次居然还没当场挂掉,简直可以称之为坚挺。我们再看看jvm线程数量:

waiting的线程在流量冲入后大量增加,几乎导致了oom,在流量波峰过去后线程数量又慢慢归于平稳

所以观察结论是大量被创建的线程导致的内存飙高,接下来就是需要观察线程快照找出罪魁祸首了

将线程堆栈导入fastthread.io/分析堆栈,我们查看线程数最多的相同线程组。

这个明显是OkHttp使用不当导致的OkHttp链接池的异常增多,我第一个反应是联想到我们的fegin调用,feign底层可能使用了OkHttp导致OKHttp链接池创建异常。

下一秒就把自己的推测推翻了,因为首先feign不可能有这么明显的问题,第二是上图中的“OkHttp api.tpns.tencent.com”我们并不是通过feign调用,而是直接通过sdk调用的(这个是tpns,腾讯的app推送)

那么,有没有可能是sdk的问题呢?

我们是这么调用腾讯的推送的:

//每次推送都会调用这段代码 
XingeApp xingeApp = new XingeApp.Builder()
                .appId(androidAppId)
                .secretKey(androidSecret)
                .domainUrl(PREFIXURL)
                .build();
 return xingeApp.pushApp(pushAppRequest);

点开build方法发现是这样的

 public XingeApp build() {
            if (appId == null || secretKey == null) {
                throw new IllegalArgumentException("Please set appId and secret key.");
            }

            return new XingeApp(this);
        }


private XingeApp(Builder builder){
        if(builder.domainUrl != null){
            restapiV3.setDomainUrl(builder.domainUrl);
        }

        this.accessId = builder.appId;
        this.secretKey = builder.secretKey;
        this.isSignAuth = builder.useSignAuth;

        client = new OkHttpClient.Builder()
                .proxy(builder.proxy)
                .connectTimeout(builder.connectTimeOut, TimeUnit.SECONDS)//设置连接超时时间
                .readTimeout(builder.readTimeOut, TimeUnit.SECONDS)//设置读取超时时间
                .build();
 }

可以看到这个new XingeApp(this)到后面是初始化了一个OkHttpClient的客户端,点进OkHttpClient的Builder方法是这样的:

public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
    //可以看到罪魁祸首就在这里
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

于是真相大白,原来ConnectionPool对象是在这里new出来的

可以看到,每次new一个XingeApp就会new一个OkHttpClient,顺便new一个ConnectionPool。而每次推送的都会new一个XingeApp导致内存中有大量的ConnectionPool对象存在,直到堆满了进行一次fgc,又能回收掉很大部分的内存,因为大部分ConnectionPool在推送完成后以后都是没用的垃圾内存,推送又是海量的,所以导致内存一直满一直fgc但是又能一直抗住(因为每次fgc可以gc掉大部分内存)

那么怎么解决呢?

最简单的解决方式就是减少XingeApp实例的对象,因为没必要每次推送都去new一个对象出来,按理说只需要一个实例就够了,OkHttpConnectionPool也只需要一个,链接的复用和摧毁完全交给OkHttp就行。所以修改如下:

//伪代码 双重校验锁
public static XingeApp getAndroidXingeAppInstance(){
        if (androidXingeAppInstance !=null){
            return androidXingeAppInstance;
        }
        synchronized (TpnsPushLogicImpl.class{
            if (androidXingeAppInstance !=null) {
                return androidXingeAppInstance;
            }
            androidXingeAppInstance = new XingeApp.Builder()
                    .appId(staticAndroidAppId)
                    .secretKey(staticAndroidSecret)
                    .domainUrl(PREFIXURL)
                    .build();
            return androidXingeAppInstance;
        }
}

public void pushMessage(){
     XingeApp xingeApp = getAndroidXingeAppInstance();
    xingeApp.pushMessage;
}

升级后在推送高峰期重新查看skywalking仪表板:

可以明显看到线程数量降下来了,gc也只有ygc,说明很健康

重新打出该应用的线程快照,导入线程快照分析工具查看okHttpConnectionPool线程

可以看到现在应用内只有一条OkHttpConnection的线程,改造成功




欢迎加入我的知识星球,全面提升技术能力。

加入方式,长按下方二维码噢

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
衙门公司来了个大佬,把 FullGC 40 次/天优化为 10 天 1 次,太秀了~!4h白嫖lululemon线上实习证书,Marketing留学生0门槛拿证!波士顿咨询BCG(UK)招聘Full Time Consulting我用“错峰学习法”,完美解决了孩子时间不够的问题!【就在今晚】剥茧抽丝,理解MG临床试验的设计及结果;由宣武医院李海峰教授主讲北大國學大師季羨林(1911年8月6日—2009年7月11日)糕妈:为了控制体重,我吃过最大的苦是这个!一直不好意思说,最近终于解决了观夏品牌创始人Elvis:线下空间是制造流量而非消耗流量鸡胚,竟然解决了流感病毒培养大难题 | 医学有故事 13“痛苦来自于发现问题,快乐来源于解决问题” | 峰瑞第5期Open Day回顾加州彩票局警告:线上代买彩票有风险,最好自己买农场的春天刚刚!明确了:线上!急需汉语教师人员!会普通话的优先!实战总结|抽丝剥茧,记一次神奇的崩溃把回形针插在手机上,解决了生活大问题,太实用!我用2张纸,解决了1个带娃旅游最容易忽视的安全隐患他们养活了小象,顺便还解决了新冠New MSI MPG A850GF 850W 80 PLUS Gold Fully Modular Power Supply频繁FullGC的原因竟然是 “开源代码”?枯叶 (世界华人微诗群命题诗)The ‘Full-Time Kid’ Life Isn’t All It’s Cracked Up to Be总喜欢白费个劲是为啥?明明一下子就解决了呀!4h白嫖lululemon线上实习证书,这届Marketing留学生“抄近路”太爽了!Lululemon开启线上实习, 4h拿下Marketing重量级证书!“大师”、中间商与运营者:线上偷拍产业调查《破茧:从石库门到天安门》新书发布中国医疗队在坦桑尼亚记实 (三)攻击性螃蟹袭击北美 上百万只被扔进垃圾场 华人痛心:一顿螃蟹宴不就解决了高端访谈 | 亚洲卓盛:线上元宇宙虚拟世界+线下真实社交世界,技术助推数字经济英国妈妈因大胸太痛苦,卖房筹钱做缩胸手术,做完如获新生:我终于解脱了!实用到爆炸!这套书解决了孩子90%的心理成长问题!攻击性螃蟹袭击加拿大 上百万只被扔进垃圾场 华人痛心:一顿螃蟹宴不就解决了Dell SE2722H 27" Full HD 75hz Monitor全网观看量近20万,618当晚的这场直播解决了企业哪些问题?
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。