tomcat数据库连接不释放
最近几个月,某项目的应用系统总是会出现tomcat数据库连接池耗尽的情况,此问题也一直困扰着我,连接池相关参数设置如下:
maxActive=200;
logAbandoned=true;
abandonWhenPercentageFull=50;
removeAbandoned=true;
removeAbandonedTimeout=60
当连接池耗尽后,
从数据库层面排查,数据库中的jdbc的session数据只有几个,说明tomcat到数据库的真实连接就这几个;
从数据库服务器服务器系统层面排查,实际上也只有几个ESTABLISHED的TCP连接;
从应用服务器操作系统层面排查,发现存在大量ESTABLISHED的TCP连接没有释放;
以为是防火墙超时导致的,从防火墙层面排查,设置的是长连接,防火墙不会断开TCP连接;
未果,重启应用。。。运行一段时间后,问题又出现
以为是参数未生效导致,于是通过byteman跟踪数据库连接池,参数实际上已经生效,以下为跟踪连接时使用的注入脚本:
# 跟踪获得连接的线程,获得前,当前连接池中活动的连接数和空闲的连接数
RULE trace tomcat connection pool number
CLASS org.apache.tomcat.jdbc.pool.ConnectionPool
METHOD getConnection
AT ENTRY
BIND thread:java.lang.Thread = java.lang.Thread.currentThread();
IF TRUE
DO traceln("*** 获得连接, 当前线程ID:" + thread.getId() + " 当前线程名:" + thread.getName() + " 连接池中活动连接数: " + $0.getActive() + " 空闲连接数:" + $0.getIdle() + "***");
# traceln("是否可abandan: " + $0.getPoolProperties().isRemoveAbandoned() + $0.getPoolProperties().getSuspectTimeout() + " 中止超时时间:" + $0.getPoolProperties().getRemoveAbandonedTimeout() + " 百分比: " + $0.getPoolProperties().getAbandonWhenPercentageFull())
ENDRULE# setMaxActive
#RULE set tomcat connection pool MaxActive
#CLASS org.apache.tomcat.jdbc.pool.ConnectionPool
#METHOD getConnection
#AT EXIT
#IF $0.getPoolProperties().getSuspectTimeout() < 1200
#DO traceln("*** 设置连接池MaxActive至400 ***");
# $0.getPoolProperties().setMaxActive(400)
#$0.getPoolProperties().setAbandonWhenPercentageFull(25)
#$0.getPoolProperties().setSuspectTimeout(1200)
#ENDRULE
# 跟踪连接返回, 返回后,当前连接池中活动的连接数和空闲的连接数
RULE trace tomcat return connection pool
CLASS org.apache.tomcat.jdbc.pool.ConnectionPool
METHOD returnConnection
AT EXIT
BIND thread:java.lang.Thread = java.lang.Thread.currentThread();
IF TRUE
DO traceln("*** 连接池大小" + $0.getSize() + " 返回连接, 当前线程ID:" + thread.getId() + " 当前线程名:" + thread.getName() + " 连接池中活动连接数: " + $0.getActive() + " 空闲连接数:" + $0.getIdle() + "***");
ENDRULE
# checkAbandoned
RULE trace tomcat checkAbandoned connection pool
CLASS org.apache.tomcat.jdbc.pool.ConnectionPool
METHOD checkAbandoned
AT ENTRY
BIND thread:java.lang.Thread = java.lang.Thread.currentThread();
IF TRUE
DO traceln("*** CHECK CONNECTION ABANDON ***");
traceln("*** 线程ID:" + thread.getId() + " 线程名:" + thread.getName() + " 活动连接: " + $0.getActive() + " 空闲连接:" + $0.getIdle() + "***")
ENDRULE# 跟踪被中止的连接
RULE trace tomcat abandon connection pool
CLASS org.apache.tomcat.jdbc.pool.ConnectionPool
METHOD abandon
AT EXIT
BIND thread:java.lang.Thread = java.lang.Thread.currentThread();
IF TRUE
DO traceln("*** CONNECTION ABANDON ***");
traceln("*** THREADID:" + thread.getId() + " THREADNAME:" + thread.getName() + " ACTIVE CONNECTION : " + $0.getActive() + " IDLE CONNECTION:" + $0.getIdle() + "***")
ENDRULE
于是问题未发现,又重启应用。。。运行一段时间后,问题又出现。。。
通过byteman跟踪每次都要写注入脚本,感觉很麻烦,后来使用arthas跟踪
使用arthas观察checkAbandoned,watch了一个小时,发现checkAbandoned根本未执行,该方法是清理线程定期执行的,与timeBetweenEvictionRunsMillis参数相关,该参数默认是30s。我使用arthas跟踪测试环境的checkAbandoned发现每一分钟调用一次,说明清理线程默认每分钟唤醒一次,我想原因找到了,出于未知原因,正式环境的连接池清理线程不能自动唤醒或者是未启动导致的。
从源码分析,看看是否是清理线程未启用导致:
发现我之前配置的参数是没问题的,满足清理线程启用的条件,说明原因是清理线程未自动唤醒,于是再次通过arthas查看线程找到了问题:
清理线程居然被阻塞了。。。 阻塞线程如下:
原来是该线程执行某条sql语句时,未读取到数据一直占用着数据库连接,而清理线程唤醒后需要获得连接从而进行清理,由于某连接被其他线程排他占用从而阻塞连接池清理线程获得此连接。
这条sql是我很早之前提出让开发优化的sql,但开发人员一直未优化。。。