通過一次生產case深入理解tomcat執行緒池
最近生產上遇到一個case,終於想明白了原因,今天週末來整理一下
生產case
最近測試istio mesh的預熱功能(呼叫端最小連線數原則)
來控制呼叫端進入k8s剛擴出來的容器的流量
因為剛啟動的JVM解釋執會導致慢請求,如果不控制流量會導致cpu突然飆升等帶來的一系列連鎖反應!
表像這裡我借用github上有個哥們的相類似提問:

翻譯一下:
首先突發流量導致執行緒突然上升到最大執行緒(800),
流量下來後還在工作的執行緒(busy threads)執行緒就下降到了 10,
但是tomcat的 currentThreadCount 仍然是 800。
根據對於執行緒池的理解,tomcat的工作執行緒空閒 60 秒(預設),它就會被回收呀,為啥一直下不來呢?
我和他只是配置有點不一樣,表像是一樣的,也是同樣的疑問
問題重點
-
為什麼流量下來後tomcat的工作執行緒居高不下遲遲得不到回收?
-
查文件或者搜google,都說設定maxIdleTime,其實它是有坑的
假設tomcat的配置如下(關鍵引數):
<!--The connectors can use a shared executor, you can define one or more named thread pools <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4" maxIdleTime="60000"/> --> <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" minSpareThreads="20" maxThreads="1024" maxConnections="10000" connectionTimeout="60000" acceptCount="150"/>
問題排查
根據tomcat原始碼
我們把上面幾個核心引數都理一遍
acceptCount
TCP SYN QUEUE佇列的長度 預設 100

connectionTimeout

預設 20000ms
對應Socket的SO_TIMEOUT屬性 是用於指定 ServerSocket.accept 和 Socket.getInputStream().read 超時的套接字選項,超時會丟擲SocketTimeoutException 【60000意味著如果超過1分鐘還沒有資料到達】
tomcat的核心流程
我們先講講下當請求進入,tomcat經歷了哪些步驟:
tcp建立相關略
-
1)Acceptor執行緒處理 socket accept
-
2)Acceptor執行緒處理 註冊registered OP_READ到多路複用器
-
3)ClientPoller執行緒 監聽多路複用器的事件(OP_READ)觸發
-
4)從tomcat的work執行緒池取一個工作執行緒來處理socket[http-nio-8080-exec-xx]
maxConnections
accptor執行緒和clientPoller執行緒的互動邏輯如下:


在這個互動中,每serverSock.accept()會被org.apache.tomcat.util.threads.LimitLatch計數 在closeSocket的時候減少計數!
LimitLatch這個物件的計數初始值就是配置的maxConnections值(預設為10000)
minSpareThreads和maxThreads
-
minSpareThreads 核心執行緒數
-
maxThreads 最大執行緒數
ClientPoller執行緒拿到read或者write事件後進行處理就會從tomcat的執行緒池拿到一個工作執行緒去處理
這裡的tomcat的Connector在建立工作執行緒池就會用到這2個引數

注意 這裡的執行緒池的keepAlivetime=60s
執行緒池相關知識( 參考我之前的文章 )
用一張圖來表達各個引數的起作用點

梳理一下
當tomcat容器啟動後

根據配置 建立 acceptor執行緒池(預設1個) poller執行緒池(預設2個)
工作執行緒池(20個根據我的配置)
假設突發流量打進來,因為我設定的maxThreads=1024
那麼會一直建立新的nio處理執行緒到1024
等後面流量下去了,由於執行緒的keepalivetime=60s
只要服務一直都有請求進來,
工作執行緒會從 queue 中搶任務,只要搶到了一個任務,它的 keepalivetime 就會重置
由於我的服務高峰過後,每分鐘的請求數量大約是 3000 ~ 4000 個,也就是說每個執行緒都有機會搶到任務,這應該就是執行緒一直存活的原因
(當然了沒有機會搶到任務的就回收了,所以也不會一直是1024)
第二個問題,既然這樣我有辦法修改工作執行緒的keepalivetime嗎
可以的,但是得換成用Executor建立的執行緒池(如下我改成了10s)
<!--The connectors can use a shared executor, you can define one or more named thread pools --> <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="1024" minSpareThreads="20" maxIdleTime="10000"/> <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" executor="tomcatThreadPool" maxConnections="10000" connectionTimeout="60000" acceptCount="150"/>
如果你配置了Executor的話,那麼Executor的建立執行緒池邏輯如下:

確認使用了maxIdleTime值來設定執行緒的keepalivetime
tomcat7配置解說官網: https://tomcat.apache.org/tomcat-7.0-doc/config/http.html
tomcat8配置解說官網: https://tomcat.apache.org/tomcat-8.0-doc/config/http.html
注意的一點是,一定要卻別理解Excutor和Connector兩種在建立執行緒池是有區別的,不能混淆了
如果用Connector建立的執行緒池是寫死60s!
由於tomcat預設都是不推薦使用共享的Executor(被註釋的), 但是在Connector裡面又不支援設定工作執行緒的maxIdleTime, 這個有點不理解為什麼這麼設計!
總結
通過這個case,帶著這些引數在tomcat裡是怎麼起作用的疑問,結合tomcat的原始碼,是次很有收穫的梳理!
- V8中的快慢屬性(圖文分解更易理解)
- EMAS Serverless到底有多便利?
- 時隔4個月我面試位元組又掛了|總結與展望
- EMAS Serverless系列~4步教你快速搭建小程式
- uni-app 從0 到 1 製作一個專案,收藏等於學會
- 聊聊客戶檔案模型的設計與管理
- Java NIO全面詳解(看這篇就夠了)
- 面試突擊74:properties和yml有什麼區別?
- 長篇圖解java反射機制及其應用場景
- 記一次 ClickHouse 效能測試
- Linux—磁碟管理
- luoguP3224 [HNOI2012]永無鄉【線段樹,並查集】
- 麻了,程式碼改成多執行緒,竟有9大問題
- 急如閃電快如風,彩虹女神躍長空,Go語言高效能Web框架Iris專案實戰-初始化專案ep00
- HC32L110 系列 M0 MCU 的介紹和Win10下DAP-Link, ST-Link, J-Link的燒錄
- 使用 Golang 程式碼生成圖表的開源庫對比
- MapReduce入門實戰
- selenium基本用法
- 紅黑樹以及JAVA實現(一)
- Docker常用命令