共計 2697 個字符,預(yù)計需要花費 7 分鐘才能閱讀完成。
這篇文章給大家分享的是有關(guān) Tomcat 9.0.26 高并發(fā)場景下 DeadLock 問題怎么處理的內(nèi)容。丸趣 TV 小編覺得挺實用的,因此分享給大家做個參考,一起跟隨丸趣 TV 小編過來看看吧。
一、Tomcat 容器 9.0.26 版本 Deadlock 問題 1.1 問題現(xiàn)象 1.1.1 發(fā)生 Deadlock 的背景
某接口 /get.do 壓測,3 分鐘后,成功事務(wù)數(shù) TPS 由 1W 驟降至 0。
1.1.2 Tomcat 服務(wù)器出現(xiàn)大量的 CLOSE_WAIT
被壓測服務(wù)器,出現(xiàn) TCP CLOSE_WAIT 狀態(tài)個數(shù)在 200~2W 左右。
1.2 初步定位:線程堆棧信息入手
通過 jstack 打印 Tomcat 堆棧信息,發(fā)現(xiàn)“Found 1 deadlock”
Found one Java-level deadlock:
=============================
http-nio-8080-exec-409 :
waiting to lock monitor 0x00007f064805aa78 (object 0x00000006c0ebf148, a java.util.HashSet),
which is held by http-nio-8080-ClientPoller
http-nio-8080-ClientPoller :
waiting to lock monitor 0x00007f05e8061058 (object 0x00000007bfe40a70, a java.lang.Object),
which is held by http-nio-8080-exec-205
http-nio-8080-exec-205 :
waiting to lock monitor 0x00007f0614018448 (object 0x00000006c0e8e088, a java.util.HashSet),
which is held by http-nio-8080-BlockPoller
http-nio-8080-BlockPoller :
waiting to lock monitor 0x0000000001ed06e8 (object 0x00000007bfe110f8, a java.lang.Object),
which is held by http-nio-8080-exec-380
http-nio-8080-exec-380 :
waiting to lock monitor 0x00007f064805aa78 (object 0x00000006c0ebf148, a java.util.HashSet),
which is held by http-nio-8080-ClientPoller [object Object]
1.2.1 快速修復(fù)方案
內(nèi)部討論后,認(rèn)為當(dāng)前 Tomcat 版本可能有 Bug。不影響項目進(jìn)度,簡單修改方案把 SpringBoot 使用的 Tomcat 9.0.26 降級到 Tomcat 8。降級后再次壓測,沒有發(fā)現(xiàn)問題。基本上可以確定 Tomcat 9.0.26 應(yīng)該是存在 Deadlock 問題。
1.3 問題進(jìn)一步跟蹤 1.3.1 向 Apache 社區(qū)的反饋
為了確認(rèn)問題,我們試著給 Tomcat 提交 Bug 反饋。
從堆棧信息來看,是 3 類線程 5 個線程由于加鎖的順序不致,從而相互等待發(fā)生了死鎖。圖形化上面加鎖的過程如下圖。
1.4 問題原因分析
明確了死鎖的過程,但是哪個環(huán)節(jié)出了問題呢。這就需要深入到源碼層去定位問題。首先需要下載 OpenJDK 源碼,然后是 Tomcat 9.0.26 的源碼。根據(jù)堆棧信息,定位到相應(yīng)的代碼位置。我們理出如下圖 Tomcat 9.0.26 死鎖流程說明。
要比較好的理解上圖,需要對于 NIO 有一定的了解。在 Tomcat 中 NIO 主要是理解 NIO Endpoint。
Poller 是對于 Selector 的一個封裝,而線程名為 exec-xx 的執(zhí)行線程是 Channel 的封裝。在 NIO 中 Channel 注冊到 Selector 然后通過 SelectionKey 來記錄對應(yīng)關(guān)系。到此,主角都上場了。
Poller 的 run 方法作為后臺線程一直在輪詢(select)準(zhǔn)備好的 SelectionKey,在輪詢的時候也順便需要把 cancelledKey 中的 SelectionKey 給反注冊。執(zhí)行線程 EXEC-XX 在處理時會先判斷連接的狀態(tài),比如失敗、異常等情況會調(diào)用 Channel 的 close 方法去關(guān)閉連接。
而 Channel 的 close 實際只是把 SelectionKey 加入到 cancelledKey。兩者都需要先鎖定,但鎖定的順序不一致,從而導(dǎo)致死鎖。
1.4.1 與 Tomcat 開發(fā)者的交流
在提交 Bug 后,很快得到了 Remy Maucherat 的回復(fù),首先他提到這個 NIO 內(nèi)部的死鎖。然后我們提到 NIO 內(nèi)部的死鎖是由于 Poller.run 和 Poller.canceledKey 在并發(fā)時導(dǎo)到的。
Remy Maucherat 很快就進(jìn)行了修復(fù),主要是把 Poller.canceledKey 中 close 移到了 finally 中去執(zhí)行,也就是先讓 Poller.run 獲得鎖。
在得到修復(fù)后,我們使用替換后的代碼進(jìn)行了再次壓測,死鎖問題沒有出現(xiàn)了。Remy Maucherat 同時提到在最新的 OpenJDK 中相關(guān)問題的修復(fù),但只會出現(xiàn)在 jdk 11 和 14 版本。
溝通中的詳情見下圖。
1.4.2 Github 上修復(fù)的驗證
https://github.com/apache/tomcat/commit/9b1a8b67bffe462fc745b19e15ed59c37e2e1dcf
1.5 結(jié)果驗證
使用
https://github.com/apache/tomcat/commit/9b1a8b67bffe462fc745b19e15ed59c37e2e1dcf 提供修復(fù)后代碼,重新打包 tomcat-embed-core.jar 替換 9.X.XX 的再次壓測,TPS 平穩(wěn)在 1.5W 左右。
到此問題基本是定位清楚,并得到了修復(fù)。Remy Maucherat 也回復(fù)到“The fix will be in Tomcat 9.0.31+”。
目前 Tomcat 最新版本是 Tomcat 9.0.30,還需要耐心等待 31 版本更新。建議使用 Tomcat 8 版本。
感謝各位的閱讀!關(guān)于“Tomcat 9.0.26 高并發(fā)場景下 DeadLock 問題怎么處理”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!