動態執行緒池(DynamicTp)之動態調整Tomcat、Jetty、Undertow執行緒池引數篇

大家好,這篇文章我們來介紹下動態執行緒池框架(DynamicTp)的adapter模組,上篇文章也大概介紹過了,該模組主要是用來適配一些第三方元件的執行緒池管理,讓第三方元件內建的執行緒池也能享受到動態引數調整,監控告警這些增強功能。

DynamicTp專案地址

目前500多star,感謝你的star,歡迎pr,業務之餘給開源貢獻一份力量

gitee地址

:https://gitee。com/yanhom/dynamic-tp

github地址

:https://github。com/lyh200/dynamic-tp

系列文章

adapter已接入元件

adapter模組目前已經接入了SpringBoot內建的三大WebServer(Tomcat、Jetty、Undertow)的執行緒池管理,實現層面也是和核心模組做了解耦,利用spring的事件機制進行通知監聽處理。

動態執行緒池(DynamicTp)之動態調整Tomcat、Jetty、Undertow執行緒池引數篇

可以看出有兩個監聽器

當監聽到配置中心配置變更時,在更新我們專案內部執行緒池後會釋出一個RefreshEvent事件,DtpWebRefreshListener監聽到該事件後會去更新對應WebServer的執行緒池引數。

同樣監控告警也是如此,在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()方法如下:

可以看出他是先呼叫父類的execute()方法,然後捕獲RejectedExecutionException異常,再去判斷如果任務佇列型別是TaskQueue,則嘗試將任務新增到任務佇列中,如果新增失敗,證明佇列已滿,然後再執行拒絕策略,此處submittedCount是一個原子變數,記錄提交到此執行緒池但未執行完成的任務數(主要在下面要提到的TaskQueue佇列的offer()方法用),為什麼要這樣設計呢?繼續往下看!

Tomcat定義了阻塞佇列TaskQueue繼承自LinkedBlockingQueue,該佇列主要重寫了offer()方法。

可以看到他在入隊之前做了幾個判斷,這裡的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核心執行緒池有對應的獲取方法,獲取方式如下

想要動態調整Tomcat執行緒池的執行緒引數,可以在引入DynamicTp依賴後,在配置檔案中新增以下配置就行,引數名稱也是和SpringBoot提供的Properties配置類引數相同,配置檔案完整示例看專案readme介紹

Tomcat執行緒池就介紹到這裡吧,透過以上的一些介紹想必大家對Tomcat執行緒池執行任務的流程都很清楚了吧。

Jetty內部執行緒池的實現

Jetty內部執行緒池,定義了一個繼承自Executor的ThreadPool頂級介面,實現類有以下幾個

動態執行緒池(DynamicTp)之動態調整Tomcat、Jetty、Undertow執行緒池引數篇

內部主要使用QueuedThreadPool這個實現類,該執行緒池執行流程就不在詳細解讀了,感興趣的可以自己去看原始碼,核心思想都差不多,圍繞核心執行緒數、最大執行緒數、任務佇列三個引數入手,跟Tocmat比對著來看,其實也挺簡單的。

Jetty執行緒池有提供public的獲取方法,獲取方式如下

想要動態調整Jetty執行緒池的執行緒引數,可以在引入DynamicTp依賴後,在配置檔案中新增以下配置就行,引數名稱也是和SpringBoot提供的Properties配置類引數相同,配置檔案完整示例看專案readme介紹

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類),執行流程可以自己去分析

動態執行緒池(DynamicTp)之動態調整Tomcat、Jetty、Undertow執行緒池引數篇

具體的建立程式碼如下,根據外部是否傳入,如果有傳入則用外部傳入的類,如果沒有,根據引數設定內部建立一個,具體是用JUC的ThreadPoolExecutor還是Jboss的EnhancedQueueExecutor,根據配置引數選擇

動態執行緒池(DynamicTp)之動態調整Tomcat、Jetty、Undertow執行緒池引數篇

Undertow執行緒池沒有提供public的獲取方法,所以透過反射來獲取,獲取方式如下

想要動態調整Undertow執行緒池的執行緒引數,可以在引入DynamicTp依賴後,在配置檔案中新增以下配置就行,配置檔案完整示例看專案readme介紹

總結

以上介紹了Tomcat、Jetty、Undertow三大WebServer內建執行緒池的一些情況,重點介紹了Tomcat的,篇幅有限,其他兩個感興趣可以自己分析,原理都差不多。同時也介紹了基於DynamicTp怎麼動態調整執行緒池的引數,當我們做WebServer效能調優時,能動態調整引數真的是非常好用的。

再次歡迎大家使用DynamicTp框架,一起完善專案。

下篇文章打算分享一個DynamicTp使用過程中因為Tomcat版本不一致導致的監控執行緒halt住的奇葩問題,透過一個問題來掌握ScheduledExecutorService的原理,歡迎大家持續關注。

聯絡我

歡迎加我微信或者關注公眾號交流,一起變強!