共计 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 问题怎么处理”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!