- 问题发现
生产应用现存在问题,影响到系统的使用,前端页面只配置了35个派生指标,后台任务生成20000多线程任务,占用了全部资源,导致其他系统也没资源可用,指标工厂也无法进一步使用,今天上午发的死锁也应该是这个原因引起的,在配置初期没有存在死锁的问题,派生指标配置的越来越多,后面配置的派生指标出现死锁的问题也越来越多,现在有5台机器,都已经快到机器的极限值,从前台配置和后台生成任务的比例来看,现在的代码应该是存在bug,或者在任务架构方面需要修改。
其中一台机器线程查询如下:
查看用户进程
[bdpcloud@host167 da]$ ps -u bdpcloud -L | wc -l
5348
统计RUNNABLE线程数量
[bdpcloud@host167 da]$ jstack 319012 > thread.txt
[bdpcloud@host167 da]$ grep RUNNABLE thread.txt | wc -l
92
- 现场日志提取
使用jstack命令提取线程日志,命令是:jstack PID
[nmportal@hhhtsjzx-app04-1-2 bin]$ /opt/nmportal/jdk1.8/bin/jstack 2625 > a.txt
2023-01-18 16:26:13
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):
"Attach Listener" #4493 daemon prio=9 os_prio=0 tid=0x00007fb3b81b9000 nid=0x5623 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"I/O dispatcher 4016" #4481 prio=5 os_prio=0 tid=0x00007fb32c51a000 nid=0x1896 runnable [0x00007fb0e0e65000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x00000000a3901d98> (a sun.nio.ch.Util$3)
- locked <0x00000000a3901d88> (a java.util.Collections$UnmodifiableSet)
- locked <0x00000000a3901da8> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:255)
at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:591)
at java.lang.Thread.run(Thread.java:748)
经过统计,类似于这个线程为RUNNABLE状态的,多达8588个,基本上确定是应用代码存在问题
- 问题排查过程
既然定位到是代码创建线程的问题,那就候找到这个代码是哪里创建的,看了一下这个报错日志,报错的类不是jdk自带的就是框架自带的,日志信息量小,感觉无从下手。我们先找到日志里面的报错信息,看报错涉及的类AbstractMultiworkerIOReactor和线程名称I/O dispatcher,还好这个写了线程的名称,我们根据类名和线程的名称找到到了代码创建的位置
但是这个也并没有什么用,无法根据这些信息查找到是创建的源头是我们的哪里的代码问题,再看一下AbstractMultiworkerIOReactor这个类,去百度搜索一下,查看一下相关信息,从某种关系来说,和httpasynclient有着某种关系,找不到关系,也只能边找百度,边尝试猜测是哪里引用了,也许是运气比较好,查找第一个类就定位到问题所在了。
我们查看代码,搜一下哪里使用了httpsyn类似的方法或者类
最终找到一个可疑的地方,在创建AsyncRestTemplate的时候,用了 @Scope("prototype"),即原型,就是多例的意思,而问题在于这个AsyncRestTemplate里面,每次都会创建一个AsyncRestTemplate都会创建一个新的HttpComponentsAsyncClientHttpRequestFactory,而这个线程池并不会关闭,所以调用了这个同步的方法,就会创建越来越多的线程。把这个 @Scope("prototype")去掉即可