一次线上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