天天看點

Tomcat 9.0.26 高并發場景下DeadLock問題排查與修複

本文主要針對deadlock問題進行分析以及通過使用Apache提供的新patch進行驗證。

本文首發于 vivo網際網路技術 微信公衆号 

連結:https://mp.weixin.qq.com/s/-OcCDI4L5GR8vVXSYhXJ7w

作者:黃衛兵、陳錦霞

一、Tomcat容器 9.0.26 版本 Deadlock 問題

1.1 問題現象

1.1.1  發生 Deadlock 的背景

某接口/get.do壓測,3分鐘後,成功事務數TPS由1W驟降至0。

Tomcat 9.0.26 高并發場景下DeadLock問題排查與修複

1.1.2  Tomcat伺服器出現大量的CLOSE_WAIT

被壓測伺服器,出現TCP CLOSE_WAIT狀态個數在200~2W左右。

Tomcat 9.0.26 高并發場景下DeadLock問題排查與修複

1.2 初步定位:線程堆棧資訊入手

通過jstack列印Tomcat堆棧資訊,發現“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"           

1.2.1  快速修複方案

内部讨論後,認為目前Tomcat版本可能有Bug。不影響項目進度,簡單修改方案把SpringBoot 使用的Tomcat 9.0.26 降級到Tomcat 8。降級後再次壓測,沒有發現問題。基本上可以确定Tomcat 9.0.26 應該是存在 Deadlock 問題。

1.3  問題進一步跟蹤

1.3.1  向Apache社群的回報

為了确認問題,我們試着給Tomcat送出Bug回報。

Tomcat 9.0.26 高并發場景下DeadLock問題排查與修複

從堆棧資訊來看,是3類線程5個線程由于加鎖的順序不緻,進而互相等待發生了死鎖。圖形化上面加鎖的過程如下圖。

Tomcat 9.0.26 高并發場景下DeadLock問題排查與修複

1.4 問題原因分析

明确了死鎖的過程,但是哪個環節出了問題呢。這就需要深入到源碼層去定位問題。首先需要下載下傳OpenJDK 源碼,然後是Tomcat 9.0.26 的源碼。根據堆棧資訊,定位到相應的代碼位置。我們理出如下圖Tomcat 9.0.26死鎖流程說明。

Tomcat 9.0.26 高并發場景下DeadLock問題排查與修複

要比較好的了解上圖,需要對于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。兩者都需要先鎖定,但鎖定的順序不一緻,進而導緻死鎖。

1.4.1  與Tomcat開發者的交流

在送出Bug後,很快得到了Remy Maucherat的回複,首先他提到這個NIO内部的死鎖。然後我們提到NIO内部的死鎖是由于Poller.run和Poller.canceledKey在并發時導到的。

Remy Maucherat很快就進行了修複,主要是把Poller.canceledKey中close移到了finally中去執行,也就是先讓Poller.run獲得鎖。

在得到修複後,我們使用替換後的代碼進行了再次壓測,死鎖問題沒有出現了。Remy Maucherat同時提到在最新的OpenJDK中相關問題的修複,但隻會出現在jdk 11和14版本。

溝通中的詳情見下圖。

Tomcat 9.0.26 高并發場景下DeadLock問題排查與修複

1.4.2  Github上修複的驗證

https://github.com/apache/tomcat/commit/9b1a8b67bffe462fc745b19e15ed59c37e2e1dcf

Tomcat 9.0.26 高并發場景下DeadLock問題排查與修複

1.5 結果驗證

使用 https://github.com/apache/tomcat/commit/9b1a8b67bffe462fc745b19e15ed59c37e2e1dcf 提供修複後代碼,重新打包tomcat-embed-core.jar 替換9.X.XX的再次壓測,TPS平穩在1.5W左右。

Tomcat 9.0.26 高并發場景下DeadLock問題排查與修複

到此問題基本是定位清楚,并得到了修複。Remy Maucherat也回複到“The fix will be in Tomcat 9.0.31+”。

目前Tomcat 最新版本是Tomcat 9.0.30,還需要耐心等待31版本更新。建議使用Tomcat 8版本。

二、相關連結與參考

  1. OpenJdk源碼下載下傳
  2. Tomcat 源碼
  3. 來自阿裡雲溪社群:斷網故障時Mtop觸發Tomcat高并發場景下的BUG排查和修複
  4. 深度解讀Tomcat中的NIO模型 

更多内容敬請關注 vivo 網際網路技術 微信公衆号

Tomcat 9.0.26 高并發場景下DeadLock問題排查與修複

注:轉載文章請先與微信号:labs2020 聯系。

分享 vivo 網際網路技術幹貨與沙龍活動,推薦最新行業動态與熱門會議。