大家好,这篇文章我们来讨论一个话题,怎么去管理 SpringBoot 内置的三大 WebServer(Tomcat、Jetty、Undertow)的线程池,包括监控告警、动态调参。
不管是应对越来越卷的面试考察,还是自己项目日常性能调优,绝对有用,学到就是赚到,搬好椅子,开始我们的分析之旅。
写在前面
要想去管理第三方组件的线程池,首先肯定要对这些组件有一定的熟悉度,了解整个请求的一个处理过程,找到对应处理请求的线程池,这些线程池不一定是 JUC 包下的 ThreadPoolExecutor 类,也可能是组件自己实现的线程池,但是基本原理都差不多。
Tomcat、Jetty、Undertow 这三个 WebServer 都是这样,他们并没有直接使用 JUC 提供的线程池实现,而是自己实现了一套,或者扩展了 JUC 的实现。翻源码找到相应的目标线程池后,然后看有没有暴露 public 方法供我们调用获取,如果没有就需要考虑通过反射来获取了。
下面我们来简单分析下这三大 WebServer 的线程池内部实现。
Tomcat 内部线程池的实现
- Tomcat 内部线程池没有直接使用 JUC 下的 ThreadPoolExecutor,而是选择继承 JUC 下的 Executor 体系类,然后重写 execute() 等方法,不同版本有差异。
1.继承 JUC 原生 ThreadPoolExecutor(9.0.50 版本及以下),并覆写了一些方法,主要 execute() 和 afterExecute()
2.继承 JUC 的 AbstractExecutorService(9.0.51 版本及以上),代码基本是拷贝 JUC 的 ThreadPoolExecutor,也相应的微调了 execute() 方法执行流程
注意 Tomcat 实现的线程池类名称也叫 ThreadPoolExecutor,名字跟 JUC 下的是一样的,Tomcat 的 ThreadPoolExecutor 类 execute() 方法如下:
public void execute(Runnable command, long timeout, TimeUnit unit) {
submittedCount.incrementAndGet();
try {
super.execute(command);
} catch (RejectedExecutionException rx) {
if (super.getQueue() instanceof TaskQueue) {
final TaskQueue queue = (TaskQueue)super.getQueue();
try {
if (!queue.force(command, timeout, unit)) {
submittedCount.decrementAndGet();
throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull"));
}
} catch (InterruptedException x) {
submittedCount.decrementAndGet();
throw new RejectedExecutionException(x);
}
} else {
submittedCount.decrementAndGet();
throw rx;
}
}
}
复制代码
可以看出他是先调用父类的 execute()方法,然后捕获 RejectedExecutionException 异常,再去判断如果任务队列类型是 TaskQueue,则尝试将任务添加到任务队列中,如果添加失败,证明队列已满,然后再执行拒绝策略,此处 submittedCount 是一个原子变量,记录提交到此线程池但未执行完成的任务数(主要在下面要提到的 TaskQueue 队列的 offer()方法用),为什么要这样设计呢?继续往下看!
- Tomcat 定义了阻塞队列 TaskQueue 继承自 Linked