一次线上elasticsearch中RestClient使用不当引起的线程溢出问题

1.问题描述

下午突然收到运维的报警提示,线上有个elk平台进程的线程数飙到1.9w个,快打满了。(问题:1个进程的最大线程数和哪些配置相关?)

来不及思考排查原因,服务快要不可用了,马上能想到的快速解决办法是重启服务。

着急忙慌的,当时忘记保存现场了。所幸的是半小时后线程数又飙到5849,好了可以开始分析了。

 

2.找到进程PID 

ps aux | grep elk 

得到PID为10345

 

3.查看线程数

(1)top -H -p 10345

(2)cat /proc/10345/status

(3)使用arthas的thread

 

4.打印线程栈信息

jstack 10345 >> jstack.dump

 

5.分析jstack.dump

将快照文件从生产机器download到本地

可以用jdk自带的VisualVM分析

推荐一个好用的网站在线分析:https://gceasy.io/

 

 

 

 

 

 发现I/O dispatcher线程组有很5600个线程,点进去找到线程栈打印的详细信息

 

 

 

6.排查问题思路

竟然全部都是nio的io线程,因为是elk项目,最先联想到是elastic的客户端异步通信的问题(这里只是猜测)

然后尝试去github找一下elasticsearch的issues,通过关键字I/O dispatcher找到了这一条:https://github.com/elastic/elasticsearch/issues/61675

虽然issue的描述和我的现象一样,说是由于es rest客户端引起的。但是目前还不能说明本次问题是这个原因。有了疑点就需要找到证据,而且这个issue里面并没有找到解决答案。

 

抱着怀疑RestClient(RestHightLevelClient)

还有线程栈打印线索AbstractMultiworkerIOReactor第588行继续深入项目代码找答案

 

正向不知道原因,那就反着来,由现象找问题。先找到AbstractMultiworkerIOReactor的588行

 

 发现是worker线程的run方法,继续找到启动的地方execute方法

 

 

然后根据execute的调用链依次找到

  -> PoolingNHttpClientConnectionManager.execute(191行)

    -> CloseableHttpAsyncClientBase.start(80行)

      -> RestClientBuilder.build(213行)

 

最后在项目里找到如下一段代码

        RestClientBuilder builder = RestClient.builder(
                new HttpHost(HOSTNAME, PORT, SCHEME))
                .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
                        .setDefaultCredentialsProvider(credentialsProvider));
        RestClient restClient = builder.build();

       //省略执行逻辑

        restClient.close();

仔细看代码本身没有太多的问题,最多也就是没有在finally代码块close

结合上下文发现这部分的build逻辑是写在util方法的静态方法中的,每调用一次都会创建一个RestClient客户端,而且关闭流程也不规范,RestClient客户端每次都会创建异步通信线程

短时间大量调用就导致了线程数激增。由于不是自己负责的项目,代码不熟悉,但我感觉破案了。

 

7.解决思路

目前的思路是不要在频繁调用的方法创建RestClient局部变量

(1)使用单例RestClient

 

posted @ 2022-07-05 14:29  OUYM  阅读(557)  评论(0编辑  收藏  举报