记一次OkHttpClient导致线程过多的排查

前言

前天,一位21世纪的好好青年正在工位上默念社会主义大法好的时候,钉钉上又报警了(公司项目接入了open-faclon监控,指标不正常会报警给钉钉的机器人),无奈默默流泪挥手告别社会主义大法开始定位线上问题。


报警内容

首先我们先来看下报警信息,为防止泄露公司信息,对项目名称和监听的端口号进行了抹除:
记一次OkHttpClient导致线程过多的排查_第1张图片

这个问题曾多次出现,一直没有抓到导致这个问题的鬼。首先先解读下这个报警内容,原因:活跃线程数过多,jmxport是监听的jmx端口号用来获取虚拟机各项信息,5118>=1500:5118代表着此时的线程数,1500是设置的报警阈值。

  • 为什么要将阈值设置为1500呢(如果你对这个阈值的设置并不感兴趣,可直接跳过阅读)?
    为了搞清楚这个阈值的建立规则,我可是下了大功夫的....操作系统可以简单的分为32位和64位,每一个32位进程都独享4G的虚拟地址空间,其中低2G是给用户的,高2G是给系统预留的。也就是每一个32位进程中,实际能够使用的内存不到2G。64位下由于寻址操作变为2的64次方基本可以看做没有限制。介绍完这个,就来介绍下博主监控程序的运行环境,4核8GB内存64为linux系统java8,程序的jvm参数为-server -Xms4g -Xmx4g -Xmn2g。-Xss 为jvm启动的每个线程分配的内存大小,如果未设置,则默认是1m(java8)。系统总内存为8G,堆内存设置为了4G,可用给分配线程的内存不会超过4G,理论上可以达到最大的线程数应该是4*1024个(实际会更少,系统文件、元空间等都会占用大量内存)。搜索了大量相关资料,只是简单的计算出了一个理想情况下线程数的最大值,对于阈值设置并没有找到一个很权威的说法。所以博主就大胆猜测,这个阈值应该是运维根据自己的经验设置的,之所以设置这个阈值是为了更好的得知服务器的运行状况,当超过这个数了,我们应当检测其原因,如果是程序代码造成的问题就需要对症下药,如果是环境问题或者流量确实达到这个阈值,我们就需要去考虑做集群做高可用。

  • 如何定位线程过多的原因
    jdk给了我们很多好用的工具,如果对jvm一点都没有了解过得同学建议大家读一下我之前的写的jvm相关的学习笔记。首先定位java项目进程pid,然后打印堆栈文件并分析堆栈文件。其过程如下图所示:
    记一次OkHttpClient导致线程过多的排查_第2张图片

    接下来我们着重来分析erp.txt这个文件,针对于线程过多的排查主要是查询程序内线程池是否使用得当,我们可以根据是否存在大量相似的线程名来判断。经过分析发现文件中大量存在OkHttp ConnectionPool,如下图所示:
    记一次OkHttpClient导致线程过多的排查_第3张图片

    经过匹配,OkHttp Connection达到4000多个,然后再次定位,发现系统中有调用php的代码使用OKhttp,代码如下所示:

    public final class HttpUtil {
    
        public static String doPost(String url, String content) throws IOException {
            OkHttpClient client = new OkHttpClient();
            RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), content);
            Request request = new Request.Builder().url(url).post(body).build();
            Response response = client.newCall(request).execute();
            return response.body().string();
        }
    
        public static String doGet(String url) throws IOException {
            OkHttpClient httpClient = new OkHttpClient();
            Request request = new Request.Builder().url(url).build();
            Response response = httpClient.newCall(request).execute();
            return response.body().string();
        }
    
    }
  • 简单了解下OkHttpClient
    线程过多是OkHttpClient导致的,我们就需要对症下药,简单了解下其用法。翻看其源码可以发现如下图注释:
    记一次OkHttpClient导致线程过多的排查_第4张图片

    简单翻译下:对于所有的http请求,建议你创造一个OkHttpClient实例并重复使用这是因为每个实例都有它自己的连接池和线程池,重用连接池和线程池可以减少延迟节省内存,下面的是OKHttpClinet的一些常见用法。至此我们已经完全清楚此次问题的来龙去脉,只需要重用OkHttpClient就好....

总结

解决这个问题非常简单,只要将OKhttpClient设置为单例,按照推荐用法去创建即可解决,不过这篇博客的价值并不在于此,而是和大家探讨一些解决问题的思路,如果你有更好的办法,欢迎留言。问题解决了,我继续默念社会主义大法好了^.^

你可能感兴趣的:(记一次OkHttpClient导致线程过多的排查)