做者 | 徐靖峰 阿里云高级开发工程师html
Dubbo 线程池满异常应该是大多数 Dubbo 用户都遇到过的一个问题,本文以 Arthas 3.1.7 版本为例,介绍如何针对该异常进行诊断,主要使用到 dashboard
/ thread
两个指令。java
Cloud Toolkit 是阿里云发布的免费本地 IDE 插件,帮助开发者更高效地开发、测试、诊断并部署应用。经过插件,能够将本地应用一键部署到任意服务器,甚至云端(ECS、EDAS、ACK、ACR 和 小程序云等);而且还内置了 Arthas 诊断、Dubbo工具、Terminal 终端、文件上传、函数计算 和 MySQL 执行器等工具。不只仅有 IntelliJ IDEA 主流版本,还有 Eclipse、Pycharm、Maven 等其余版本。git
理解线程池满异常须要首先了解 Dubbo 线程模型,官方文档:http://dubbo.apache.org/zh-cn/docs/user/demos/thread-model.html。github
简单归纳下 Dubbo 默认的线程模型:Dubbo 服务端每次接收到一个 Dubbo 请求,便交给一个线程池处理,该线程池默认有 200 个线程,若是 200 个线程都不处于空闲状态,则客户端会报出以下异常:数据库
Caused by: java.util.concurrent.ExecutionException: org.apache.dubbo.remoting.RemotingException: Server side(192.168.1.101,20880) threadpool is exhausted ...
服务端会打印 WARN 级别的日志:apache
[DUBBO] Thread pool is EXHAUSTED!
引起该异常的缘由主要有如下几点:小程序
缘由可能不少,但究其根本,都是由于业务上出了问题,致使 Dubbo 线程池资源耗尽了。因此出现该问题,首先要作的是:排查业务异常。服务器
紧接着针对本身的业务场景对 Dubbo 进行调优:网络
另外,不止 Dubbo 如此设计线程模型,绝大多数服务治理框架、 HTTP 服务器都有业务线程池的概念,因此理论上它们都会有线程池满异常的可能,解决方案也相似。多线程
那既然问题都解释清楚了,咱们还须要排查什么呢?
通常在线上,有不少运行中的服务,这些服务都是共享一个 Dubbo 服务端线程池,可能由于某个服务的问题,致使整个应用被拖垮,因此须要排查是否是集中出如今某个服务上,再针对排查这个服务的业务逻辑;须要定位到线程堆栈,揪出致使线程池满的元凶。
定位该问题,个人习惯通常是使用 Arthas 的 dashboard
和 thread
命令,而在介绍这两个命令以前,咱们先人为构造一个 Dubbo 线程池满异常的例子。
dubbo.protocol.threads=10
默认大小是 200,不利于重现该异常。
@Service(version = "1.0.0") public class DemoServiceImpl implements DemoService { @Override public String sayHello(String name) { sleep(); return "Hello " + name; } private void sleep() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }
sleep
方法模拟了一个耗时操做,主要是为了让服务端线程池耗尽。
for (int i = 0; i < 20; i++) { new Thread(() -> { while (true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } try { demoService.sayHello("Provider"); } catch (Exception e) { e.printStackTrace(); } } }).start(); }
客户端
(客户端异常)
服务端
(服务端异常)
问题得以复现,保留该现场,并假设咱们并不知晓 sleep 的耗时逻辑,使用 Arthas 来进行排查。
$ dashboard
执行效果:
(dashboard)
能够看到如上所示的面板,显示了一些系统的运行信息,这里主要关注 THREAD 面板,介绍一下各列的含义:
分:秒
在空闲状态下线程应该是处于 WAITING 状态,而由于 sleep 的缘故,如今全部的线程均处于 TIME_WAITING 状态,致使后来的请求被处理时,抛出了线程池满的异常。
在实际排查中,须要抽查必定数量的 Dubbo 线程,记录他们的线程编号,看看它们到底在处理什么服务请求。使用以下命令能够根据线程池名筛选出 Dubbo 服务端线程:
dashboard | grep "DubboServerHandler"
使用 dashboard
筛选出个别线程 id 后,它的使命就完成了,剩下的操做交给 thread
命令来完成。其实,dashboard
中的 thread
模块,就是整合了 thread
命令,可是 dashboard
还能够观察内存和 GC 状态,视角更加全面,因此我我的建议,在排查问题时,先使用 dashboard
纵观全局信息。
thread 使用示例:
$ thread -n 3
(thread -n)
$ thread
和 dashboard
中显示一致。
$ thread -b No most blocking thread found! Affect(row-cnt:0) cost in 22 ms.
这个命令还有待完善,目前只支持找出 synchronized 关键字阻塞住的线程, 若是是 java.util.concurrent.Lock
, 目前还不支持。
$ thread --state TIMED_WAITING
(thread --state)
线程状态一共有 [RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, NEW, TERMINATED] 6 种。
$ thread 46
(thread ${thread_id})
介绍了几种常见的用法,在实际排查中须要针对咱们的现场作针对性的分析,也同时考察了咱们对线程状态的了解程度。我这里列举了几种常见的线程状态:
新建立了一个线程对象,但尚未调用 start() 方法。
Java 线程将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程阻塞于锁。
进入该状态的线程须要等待其余线程作出一些特定动做(通知或中断):
该状态不一样于 WAITING,它能够在指定的时间后自行返回。
标识线程执行完毕。
(线程状态)
分析线程池满异常并无通法,须要灵活变通,咱们对下面这些 case 一个个分析:
thread --state
定位到;thread -n
来定位出最繁忙的线程;thread
命令来排查了;thread ${thread_id}
定向的查看堆栈,若是统计到大量的堆栈都是一个服务时,基本能够判定是该服务出了问题,至于说是该服务请求量忽然激增,仍是该服务依赖的某个下游服务忽然出了问题,仍是该服务访问的数据库断了,那就得根据堆栈去判断了。本文以 Dubbo 线程池满异常做为引子,介绍了线程类问题该如何分析,以及如何经过 Arthas 快速诊断线程问题。有了 Arthas,基本再也不须要 jstack 将 16 进制转来转去了,大大提高了诊断速度。
Arthas 官方举行了征文活动,第二期征文活动于 5 月 8 日 - 6 月 8 日举办,若是你有:
欢迎参加征文活动,还有奖品拿哦~点击了解详情。