大家好,这篇文章我们来介绍下动态线程池框架(DynamicTp)的adapter模块,上篇文章也大概介绍过了,该模块主要是用来适配一些第三方组件的线程池管理,让第三方组件内置的线程池也能享受到动态参数调整,监控告警这些增强功能。


DynamicTp项目地址

目前500多star,感谢你的star,欢迎pr,业务之余给开源贡献一份力量

gitee地址https://gitee.com/yanhom/dynamic-tp

github地址https://github.com/lyh200/dynamic-tp


系列文章

美团动态线程池实践思路,开源了

动态线程池框架(DynamicTp),监控及源码解析篇


adapter已接入组件

adapter模块目前已经接入了SpringBoot内置的三大WebServer(Tomcat、Jetty、Undertow)的线程池管理,实现层面也是和核心模块做了解耦,利用spring的事件机制进行通知监听处理。

可以看出有两个监听器

  1. 当监听到配置中心配置变更时,在更新我们项目内部线程池后会发布一个RefreshEvent事件,DtpWebRefreshListener监听到该事件后会去更新对应WebServer的线程池参数。

  2. 同样监控告警也是如此,在DtpMonitor中执行监控任务时会发布CollectEvent事件,DtpWebCollectListener监听到该事件后会去采集相应WebServer的线程池指标数据。

要想去管理第三方组件的线程池,首先肯定要对这些组件有一定的熟悉度,了解整个请求的一个处理过程,找到对应处理请求的线程池,这些线程池不一定是JUC包下的ThreadPoolExecutor类,也可能是组件自己实现的线程池,但是基本原理都差不多。

Tomcat、Jetty、Undertow这三个都是这样,他们并没有直接使用JUC提供的线程池实现,而是自己实现了一套,或者扩展了JUC的实现;翻源码找到相应的线程池后,然后看有没有暴露public方法供我们调用获取,如果没有就需要考虑通过反射来拿了。


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继承自LinkedBlockingQueue,该队列主要重写了offer()方法。
 @Override
public boolean offer(Runnable o) {
//we can't do any checks
if (parent==null) return super.offer(o);
//we are maxed out on threads, simply queue the object
if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
//we have idle threads, just add it to the queue
if (parent.getSubmittedCount()<=(parent.getPoolSize())) return super.offer(o);
//if we have less threads than maximum force creation of a new thread
if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
//if we reached here, we need to add it to the queue
return super.offer(o);
}

可以看到他在入队之前做了几个判断,这里的parent就是所属的线程池对象

1.如果parent为null,直接调用父类offer方法入队

2.如果当前线程数等于最大线程数,则直接调用父类offer()方法入队

3.如果当前未执行的任务数量小于等于当前线程数,仔细思考下,是不是说明有空闲的线程呢,那么直接调用父类offer()入队后就马上有线程去执行它

4.如果当前线程数小于最大线程数量,则直接返回false,然后回到JUC线程池的执行流程回想下,是不是就去添加新线程去执行任务了呢

5.其他情况都直接入队

  • 因为Tomcat线程池主要是来做IO任务的,做这一切的目的主要也是为了以最小代价的改动更好的支持IO密集型的场景,JUC自带的线程池主要是适合于CPU密集型的场景,可以回想一下JUC原生线程池ThreadPoolExecutor#execute()方法的执行流程

1.判断如果当前线程数小于核心线程池,则新建一个线程来处理提交的任务

2.如果当前线程数大于核心线程数且队列没满,则将任务放入任务队列等待执行

3.如果当前当前线程池数大于核心线程池,小于最大线程数,且任务队列已满,则创建新的线程执行提交的任务

4.如果当前线程数等于最大线程数,且队列已满,则拒绝该任务

可以看出当当前线程数大于核心线程数时,JUC原生线程池首先是把任务放到队列里等待执行,而不是先创建线程执行。

如果Tomcat接收的请求数量大于核心线程数,请求就会被放到队列中,等待核心线程处理,这样会降低请求的总体处理速度,所以Tomcat并没有使用JUC原生线程池,利用TaskQueue的offer()方法巧妙的修改了JUC线程池的执行流程,改写后Tomcat线程池执行流程如下:

1.判断如果当前线程数小于核心线程池,则新建一个线程来处理提交的任务

2.如果当前当前线程池数大于核心线程池,小于最大线程数,则创建新的线程执行提交的任务

3.如果当前线程数等于最大线程数,则将任务放入任务队列等待执行

4.如果队列已满,则执行拒绝策略

  • Tomcat核心线程池有对应的获取方法,获取方式如下
    public Executor doGetTp(WebServer webServer) {
TomcatWebServer tomcatWebServer = (TomcatWebServer) webServer;
return tomcatWebServer.getTomcat().getConnector().getProtocolHandler().getExecutor();
}
  • 想要动态调整Tomcat线程池的线程参数,可以在引入DynamicTp依赖后,在配置文件中添加以下配置就行,参数名称也是和SpringBoot提供的Properties配置类参数相同,配置文件完整示例看项目readme介绍
spring:
dynamic:
tp:
// 其他配置项
tomcatTp: # tomcat web server线程池配置
minSpare: 100 # 核心线程数
max: 400 # 最大线程数

Tomcat线程池就介绍到这里吧,通过以上的一些介绍想必大家对Tomcat线程池执行任务的流程都很清楚了吧。


Jetty内部线程池的实现

  • Jetty内部线程池,定义了一个继承自Executor的ThreadPool顶级接口,实现类有以下几个

  • 内部主要使用QueuedThreadPool这个实现类,该线程池执行流程就不在详细解读了,感兴趣的可以自己去看源码,核心思想都差不多,围绕核心线程数、最大线程数、任务队列三个参数入手,跟Tocmat比对着来看,其实也挺简单的。
public void execute(Runnable job)
{
// Determine if we need to start a thread, use and idle thread or just queue this job
int startThread;
while (true)
{
// Get the atomic counts
long counts = _counts.get(); // Get the number of threads started (might not yet be running)
int threads = AtomicBiInteger.getHi(counts);
if (threads == Integer.MIN_VALUE)
throw new RejectedExecutionException(job.toString()); // Get the number of truly idle threads. This count is reduced by the
// job queue size so that any threads that are idle but are about to take
// a job from the queue are not counted.
int idle = AtomicBiInteger.getLo(counts); // Start a thread if we have insufficient idle threads to meet demand
// and we are not at max threads.
startThread = (idle <= 0 && threads < _maxThreads) ? 1 : 0; // The job will be run by an idle thread when available
if (!_counts.compareAndSet(counts, threads + startThread, idle + startThread - 1))
continue; break;
} if (!_jobs.offer(job))
{
// reverse our changes to _counts.
if (addCounts(-startThread, 1 - startThread))
LOG.warn("{} rejected {}", this, job);
throw new RejectedExecutionException(job.toString());
} if (LOG.isDebugEnabled())
LOG.debug("queue {} startThread={}", job, startThread); // Start a thread if one was needed
while (startThread-- > 0)
startThread();
}
  • Jetty线程池有提供public的获取方法,获取方式如下
    public Executor doGetTp(WebServer webServer) {
JettyWebServer jettyWebServer = (JettyWebServer) webServer;
return jettyWebServer.getServer().getThreadPool();
}
  • 想要动态调整Jetty线程池的线程参数,可以在引入DynamicTp依赖后,在配置文件中添加以下配置就行,参数名称也是和SpringBoot提供的Properties配置类参数相同,配置文件完整示例看项目readme介绍
spring:
dynamic:
tp:
// 其他配置项
jettyTp: # jetty web server线程池配置
min: 100 # 核心线程数
max: 400 # 最大线程数

Undertow内部线程池的实现

  • Undertow因为其性能彪悍,轻量,现在用的还是挺多的,wildfly(前身Jboss)从8开始内部默认的WebServer用Undertow了,之前是Tomcat吧。了解Undertow的小伙伴应该知道,他底层是基于XNIO框架(3.X之前)来做的,这也是Jboss开发的一款基于java nio的优秀网络框架。但Undertow宣布从3.0开始底层网络框架要切换成Netty了,官方给的原因是说起网络编程,Netty已经是事实上标准,用Netty的好处远大于XNIO能提供的,所以让我们期待3.0的发布吧,只可惜三年前就宣布了,至今也没动静,不知道是夭折了还是咋的,说实话,改动也挺大的,看啥时候发布吧,以下的介绍是基于Undertow 2.x版本来的

  • Undertow内部是定义了一个叫TaskPool的线程池顶级接口,该接口有如图所示的几个实现。其实这几个实现类都是采用组合的方式,内部都维护一个JUC的Executor体系类或者维护Jboss提供的EnhancedQueueExecutor类(也继承JUC ExecutorService类),执行流程可以自己去分析

  • 具体的创建代码如下,根据外部是否传入,如果有传入则用外部传入的类,如果没有,根据参数设置内部创建一个,具体是用JUC的ThreadPoolExecutor还是Jboss的EnhancedQueueExecutor,根据配置参数选择

  • Undertow线程池没有提供public的获取方法,所以通过反射来获取,获取方式如下
    public Executor doGetTp(WebServer webServer) {

        UndertowWebServer undertowWebServer = (UndertowWebServer) webServer;
Field undertowField = ReflectionUtils.findField(UndertowWebServer.class, "undertow");
if (Objects.isNull(undertowField)) {
return null;
}
ReflectionUtils.makeAccessible(undertowField);
Undertow undertow = (Undertow) ReflectionUtils.getField(undertowField, undertowWebServer);
if (Objects.isNull(undertow)) {
return null;
}
return undertow.getWorker();
}
  • 想要动态调整Undertow线程池的线程参数,可以在引入DynamicTp依赖后,在配置文件中添加以下配置就行,配置文件完整示例看项目readme介绍
spring:
dynamic:
tp:
// 其他配置项
undertowTp: # undertow web server线程池配置
coreWorkerThreads: 100 # worker核心线程数
maxWorkerThreads: 400 # worker最大线程数
workerKeepAlive: 60 # 空闲线程超时时间

总结

以上介绍了Tomcat、Jetty、Undertow三大WebServer内置线程池的一些情况,重点介绍了Tomcat的,篇幅有限,其他两个感兴趣可以自己分析,原理都差不多。同时也介绍了基于DynamicTp怎么动态调整线程池的参数,当我们做WebServer性能调优时,能动态调整参数真的是非常好用的。

再次欢迎大家使用DynamicTp框架,一起完善项目。

下篇文章打算分享一个DynamicTp使用过程中因为Tomcat版本不一致导致的监控线程halt住的奇葩问题,通过一个问题来掌握ScheduledExecutorService的原理,欢迎大家持续关注。


联系我

欢迎加我微信或者关注公众号交流,一起变强!

公众号:CodeFox

微信:yanhom1314

动态线程池(DynamicTp)之动态调整Tomcat、Jetty、Undertow线程池参数篇的更多相关文章

  1. 调整Tomcat的并发线程到5000+

    调整Tomcat的并发线程数到5000+ 1. 调整server.xml的配置 先调整maxThreads的数值,在未调整任何参数之前,默认的并发线程可以达到40. 调整此项后可以达到1800左右. ...

  2. Tomcat 连接数与线程池详解

    前言 在使用tomcat时,经常会遇到连接数.线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector). 在前面的文章 详解Tomcat配置文件server.xm ...

  3. 详解tomcat连接数和线程数

    前言 在使用tomcat时,经常会遇到连接数.线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector). 在前面的文章 详解Tomcat配置文件server.xm ...

  4. DBCP,C3P0与Tomcat jdbc pool 连接池的比较

    hibernate开发组推荐使用c3p0; spring开发组推荐使用dbcp(dbcp连接池有weblogic连接池同样的问题,就是强行关闭连接或数据库重启后,无法reconnect,告诉连接被重置 ...

  5. 历时2月,动态线程池 DynamicTp 发布里程碑版本 V1.0.8

    关于 DynamicTp DynamicTp 是一个基于配置中心实现的轻量级动态线程池管理工具,主要功能可以总结为动态调参.通知报警.运行监控.三方包线程池管理等几大类. 经过多个版本迭代,目前最新版 ...

  6. 开源动态可监控线程池DynamicTp介绍

    前言 使用线程池 ThreadPoolExecutor 过程中你是否有以下痛点呢? 代码中创建了一个 ThreadPoolExecutor,但是不知道那几个核心参数设置多少比较合适 凭经验设置参数值, ...

  7. Tomcat中的线程池StandardThreadExecutor

    之所以今天讨论它,因为在motan的的NettyServer中利用它这个线程池可以作为业务线程池,它定制了一个自己的线程池.当然还是基于jdk中的ThreadExecutor中的构造方法和execut ...

  8. paip.输入法编程---智能动态上屏码儿长调整--.txt

    paip.输入法编程---智能动态上屏码儿长调整--.txt 作者Attilax ,  EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csd ...

  9. Tomcat如何使用线程池处理远程并发请求

    Tomcat如何使用线程池处理远程并发请求 通过了解学习tomcat如何处理并发请求,了解到线程池,锁,队列,unsafe类,下面的主要代码来自 java-jre: sun.misc.Unsafe j ...

随机推荐

  1. X000001

    一些相互无关联的题目的集合 都是码量不大,略有思维难度的题 做起来还是很舒适的 P6312 [PA2018]Palindrom 空间限制很小,不足以存下整个字符串,故暴力判断不可行. 考虑使用字符串哈 ...

  2. 分布式事务框架-Litx补偿事务框架源码解析

    前言 之前某段时间在研究分布式事务过程中,对实现原理比较好奇,于是去Gitee上找了几个人气比较高的框架进行学习,其中印象深刻的有Litx,因为Litx源码不多,且都是基于Spring和Dubbo底层 ...

  3. canvas绘制“飞机大战”小游戏,真香!

    canvas是ArkUI开发框架里的画布组件,常用于自定义绘制图形.因为其轻量.灵活.高效等优点,被广泛应用于UI界面开发中. 本期,我们将为大家介绍canvas组件的使用. 一.canvas介绍 1 ...

  4. 基于Docker部署4.2 版本的zabbix监控平台

    准备工作 两台VMware 虚拟机 一台充当zabbix server(安装docker)ip:192.168.73.133 一台充当zabbix agent(安装docker)ip:192.168. ...

  5. Jenkins--持续集成及Jenkins介绍

    Jenkins--持续集成及Jenkins介绍 目录 Jenkins--持续集成及Jenkins介绍 一.持续集成及Jenkins介绍 1. 软件开发生命周期 1.1 需求分析 1.2 设计 1.3 ...

  6. Docker的数据管理、网络通信和dockerfile

    Docker的数据管理.网络通信和dockerfile 目录 Docker的数据管理.网络通信和dockerfile 一.Docker的数据管理 1. 数据卷 1.1 数据卷定义 1.2 数据卷配置 ...

  7. Jenkins敏捷开发 自动化构建工具

    一.序言 Jenkins 是一款自动化构建工具,能够基于 Maven 构建后端 Java 项目,也能够基于 nodejs 构建前端 vue 项目,并且有可视化 web 界面. 所谓自动化构建是按照一定 ...

  8. SQL 游标 指针

    DECLARE @radioScoreRate decimal DECLARE @checkScoreRate decimal DECLARE @judgeScoreRate decimal DECL ...

  9. VUE动态生成table表格(element-ui)(新增/删除)

    (直接复制即可测试) 结构(红色部分 data/prop/v-model 数据绑定): <template> <el-table size="small" :da ...

  10. git本地仓库关联多个远程仓库及取消关联

    关联 本地和远程仓库关联步骤: 在网站上(如gitee.github等,这里以gitee为例)首先添加好自己的ssh公钥 本地创建一个空的项目,即文件夹. 在文件夹下git init初始化仓库 在gi ...