本文首發於 vivo互聯網技術 微信公衆號
連接: https://mp.weixin.qq.com/s/-OcCDI4L5GR8vVXSYhXJ7w
做者:黃衛兵、陳錦霞
某接口/get.do壓測,3分鐘後,成功事務數TPS由1W驟降至0。java
被壓測服務器,出現TCP CLOSE_WAIT狀態個數在200~2W左右。git
經過jstack打印Tomcat堆棧信息,發現「Found 1 deadlock」github
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"
內部討論後,認爲當前Tomcat版本可能有Bug。不影響項目進度,簡單修改方案把SpringBoot 使用的Tomcat 9.0.26 降級到Tomcat 8。降級後再次壓測,沒有發現問題。基本上能夠肯定Tomcat 9.0.26 應該是存在 Deadlock 問題。apache
爲了確認問題,咱們試着給Tomcat提交Bug反饋。tomcat
從堆棧信息來看,是3類線程5個線程因爲加鎖的順序不致,從而相互等待發生了死鎖。圖形化上面加鎖的過程以下圖。服務器
明確了死鎖的過程,可是哪一個環節出了問題呢。這就須要深刻到源碼層去定位問題。首先須要下載OpenJDK 源碼,而後是Tomcat 9.0.26 的源碼。根據堆棧信息,定位到相應的代碼位置。咱們理出以下圖Tomcat 9.0.26死鎖流程說明。微信
要比較好的理解上圖,須要對於NIO有必定的瞭解。在Tomcat中NIO主要是理解NIO Endpoint。併發
Poller是對於Selector的一個封裝,而線程名爲exec-xx的執行線程是Channel的封裝。在NIO中Channel註冊到Selector而後經過SelectionKey來記錄對應關係。到此,主角都上場了。高併發
Poller的run方法做爲後臺線程一直在輪詢(select)準備好的SelectionKey,在輪詢的時候也順便須要把cancelledKey中的SelectionKey給反註冊。執行線程EXEC-XX在處理時會先判斷鏈接的狀態,好比失敗、異常等狀況會調用Channel的close方法去關閉鏈接。阿里雲
而Channel的close實際只是把SelectionKey加入到cancelledKey。二者都須要先鎖定,但鎖定的順序不一致,從而致使死鎖。
在提交Bug後,很快獲得了Remy Maucherat的回覆,首先他提到這個NIO內部的死鎖。而後咱們提到NIO內部的死鎖是因爲Poller.run和Poller.canceledKey在併發時導到的。
Remy Maucherat很快就進行了修復,主要是把Poller.canceledKey中close移到了finally中去執行,也就是先讓Poller.run得到鎖。
在獲得修復後,咱們使用替換後的代碼進行了再次壓測,死鎖問題沒有出現了。Remy Maucherat同時提到在最新的OpenJDK中相關問題的修復,但只會出如今jdk 11和14版本。
溝通中的詳情見下圖。
https://github.com/apache/tomcat/commit/9b1a8b67bffe462fc745b19e15ed59c37e2e1dcf
使用 https://github.com/apache/tomcat/commit/9b1a8b67bffe462fc745b19e15ed59c37e2e1dcf 提供修復後代碼,從新打包tomcat-embed-core.jar 替換9.X.XX的再次壓測,TPS平穩在1.5W左右。
到此問題基本是定位清楚,並獲得了修復。Remy Maucherat也回覆到「The fix will be in Tomcat 9.0.31+」。
目前Tomcat 最新版本是Tomcat 9.0.30,還須要耐心等待31版本更新。建議使用Tomcat 8版本。
更多內容敬請關注 vivo 互聯網技術 微信公衆號
注:轉載文章請先與微信號:labs2020 聯繫。