Tomcat 9.0.26高并发场景下DeadLock问题怎么处理

62次阅读
没有评论

共计 2697 个字符,预计需要花费 7 分钟才能阅读完成。

这篇文章给大家分享的是有关 Tomcat 9.0.26 高并发场景下 DeadLock 问题怎么处理的内容。丸趣 TV 小编觉得挺实用的,因此分享给大家做个参考,一起跟随丸趣 TV 小编过来看看吧。

一、Tomcat 容器 9.0.26 版本 Deadlock 问题 1.1 问题现象 1.1.1  发生 Deadlock 的背景

某接口 /get.do 压测,3 分钟后,成功事务数 TPS 由 1W 骤降至 0。

1.1.2  Tomcat 服务器出现大量的 CLOSE_WAIT

被压测服务器,出现 TCP CLOSE_WAIT 状态个数在 200~2W 左右。

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 [object Object]

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 反馈。

从堆栈信息来看,是 3 类线程 5 个线程由于加锁的顺序不致,从而相互等待发生了死锁。图形化上面加锁的过程如下图。

1.4 问题原因分析

明确了死锁的过程,但是哪个环节出了问题呢。这就需要深入到源码层去定位问题。首先需要下载 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。两者都需要先锁定,但锁定的顺序不一致,从而导致死锁。

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 版本。

沟通中的详情见下图。

1.4.2  Github 上修复的验证

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

1.5 结果验证

使用  
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 版本。

感谢各位的阅读!关于“Tomcat 9.0.26 高并发场景下 DeadLock 问题怎么处理”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计2697字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)