本文首发于 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。

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"

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

二、相关链接与参考

  1. OpenJdk源码下载

  2. Tomcat 源码

  3. 来自阿里云溪社区:断网故障时Mtop触发Tomcat高并发场景下的BUG排查和修复

  4. 深度解读Tomcat中的NIO模型

更多内容敬请关注 vivo 互联网技术 微信公众号

注:转载文章请先与微信号:labs2020 联系。

Tomcat 9.0.26 高并发场景下DeadLock问题排查与修复的更多相关文章

  1. Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S

    Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ...

  2. 高并发场景下System.currentTimeMillis()的性能问题的优化 以及SnowFlakeIdWorker高性能ID生成器

    package xxx; import java.sql.Timestamp; import java.util.concurrent.*; import java.util.concurrent.a ...

  3. 高并发场景下System.currentTimeMillis()的性能问题的优化

    高并发场景下System.currentTimeMillis()的性能问题的优化 package cn.ucaner.alpaca.common.util.key; import java.sql.T ...

  4. 【转】记录PHP、MySQL在高并发场景下产生的一次事故

    看了一篇网友日志,感觉工作中值得借鉴,原文如下: 事故描述 在一次项目中,上线了一新功能之后,陆陆续续的有客服向我们反应,有用户的个别道具数量高达42亿,但是当时一直没有到证据表示这是,确实存在,并且 ...

  5. HttpClient在高并发场景下的优化实战

    在项目中使用HttpClient可能是很普遍,尤其在当下微服务大火形势下,如果服务之间是http调用就少不了跟http客户端找交道.由于项目用户规模不同以及应用场景不同,很多时候可能不需要特别处理也. ...

  6. C++高并发场景下读多写少的解决方案

    C++高并发场景下读多写少的解决方案 概述 一谈到高并发的解决方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也 ...

  7. C++高并发场景下读多写少的优化方案

    概述 一谈到高并发的优化方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也能很大的影响整体性能,本文从单模块下读 ...

  8. 高并发场景下System.currentTimeMillis()的性能优化

    一.前言 System.currentTimeMillis()的调用比new一个普通对象要耗时的多(具体耗时高出多少我也不知道,不过听说在100倍左右),然而该方法又是一个常用方法, 有时不得不使用, ...

  9. MySQL在大数据、高并发场景下的SQL语句优化和"最佳实践"

    本文主要针对中小型应用或网站,重点探讨日常程序开发中SQL语句的优化问题,所谓“大数据”.“高并发”仅针对中小型应用而言,专业的数据库运维大神请无视.以下实践为个人在实际开发工作中,针对相对“大数据” ...

  10. 高并发场景下的httpClient优化使用

    1.背景 我们有个业务,会调用其他部门提供的一个基于http的服务,日调用量在千万级别.使用了httpclient来完成业务.之前因为qps上不去,就看了一下业务代码,并做了一些优化,记录在这里. 先 ...

随机推荐

  1. Llinux登录后出现-bash-4.2#,解决办法以及造成这样的原因

    版权声明:原创作品,谢绝转载!否则将追究法律责任. ----- 作者:kirin 1.原因是root在/root下面的几个配置文件丢失,丢失文件如下: 1..bash_profile 2..bashr ...

  2. 【GIT】学习day01 | 内嵌git安装教程【外包杯】

    Git是一个开源的分布式版本控制系统,可以有效.高速地处理从很小到非常大的项目版本管理 第一步:下载Git 下载地址https://git-scm.com/downloads 如果出现下面这种情况无法 ...

  3. git可视化管理工具有哪些?

    在 Git 中,有许多可视化管理工具可供使用.以下是一些常见的 Git 可视化管理工具及其功能的详细介绍. GitHub Desktop: GitHub Desktop 是由 GitHub 官方推出的 ...

  4. MySQL运维12-Mycat分库分表之按天分片

    一.按天分片 指定一个时间周期,将数据写入一个数据节点中,例如:第1-10天的数据,写入到第一个数据节点中,第2-20天的数据写入到第二个节点中,第3-30天的数据节点写入到第三个数据节点中. 说明1 ...

  5. 安装了华企盾DSC防泄密,所有进程的加密文件都无法打开

    用pchunter等工具查看系统回调中是否有文件厂商不存在的(system目录的除外),在恢复模式删除掉,或者用360系统急救箱查杀一下

  6. MinIO客户端之mb

    MinIO提供了一个命令行程序mc用于协助用户完成日常的维护.管理类工作. 官方资料 mc mb MinIO对象锁定 桶版本控制 桶复制 站点复制概述 创建桶bkt1,命令如下: ./mc mb lo ...

  7. C#中对比两个对象是否相等最佳实践,IEquatable和IEqualityComparer的差异

    前言 IEquatable<T> IEqualityComparer<T> 后言 参考 前言 IEquatable<T> 和 IEqualityComparer&l ...

  8. 关于向上转型以及向下转型、instanceof的一些应用。

    一.前言 在Java编程中,我们常常遇到各种类型转换的情况,尤其是在处理继承关系的类时.本文将深入探讨Java中的向上转型(upcasting).向下转型(downcasting)以及instance ...

  9. 如何使用loki查询日志中大于某一数字的值的日志

    简介 loki是一款轻量级的日志收集中间件,比elk体系占用的内存更小,采用go语言开发,可以利用grafana来查询loki中存储的日志,loki存储日志只对提前预设的标签做索引,所以日志存储空间占 ...

  10. 云图说 | 快速创建一个kubernetes集群

    摘要:3分钟快速创建您的第一个kubernetes虚拟机集群. 随着应用程序开发向基于容器的方向发展,编排和管理资源的需求变得越来越重要.Kubernetes是一个开源的.功能强大的容器编排系统,用于 ...