分析Spring/JPA/Mysql/Tomcat应用程序中的Connection Closed Exception

ncm*_*han 12 java spring tomcat hibernate connection-pooling

问题

我最近负责一个Java Web应用程序,代码已经编写完成.该应用程序接收适度高流量,并在每天上午11点至下午3点之间达到高峰时段.该应用程序使用Spring,JPA(Hibernate),MYSQL DB.Spring已配置为使用tomcat jdbc连接池来建立与DB的连接.(帖子末尾的配置细节)

在过去的几天里,在应用程序的高峰负载时间内,由于tomcat对请求没有响应,应用程序已经停止运行.它需要多次重启tomcat.

通过tomcat catalina.out日志,我注意到了很多

Caused by: java.sql.SQLException: Connection has already been closed.
    at org.apache.tomcat.jdbc.pool.ProxyConnection.invoke(ProxyConnection.java:117)
    at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:109)
    at org.apache.tomcat.jdbc.pool.DisposableConnectionFacade.invoke(DisposableConnectionFacade.java:80)
    at com.sun.proxy.$Proxy28.prepareStatement(Unknown Source)
    at org.hibernate.jdbc.AbstractBatcher.getPreparedStatement(AbstractBatcher.java:505)
    at org.hibernate.jdbc.AbstractBatcher.getPreparedStatement(AbstractBatcher.java:423)
    at org.hibernate.jdbc.AbstractBatcher.prepareQueryStatement(AbstractBatcher.java:139)
    at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1547)
    at org.hibernate.loader.Loader.doQuery(Loader.java:673)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236)
    at org.hibernate.loader.Loader.loadCollection(Loader.java:1994)
    ... 115 more
Run Code Online (Sandbox Code Playgroud)

这些经常在崩溃之前出现.

在这些异常之前更进一步,我注意到在Connection Closed异常之前放弃了很多Connections.

WARNING: Connection has been abandoned PooledConnection[com.mysql.jdbc.Connection@543c2ab5]:java.lang.Exception
    at org.apache.tomcat.jdbc.pool.ConnectionPool.getThreadDump(ConnectionPool.java:1065)
    at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:782)
    at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:618)
    at org.apache.tomcat.jdbc.pool.ConnectionPool.getConnection(ConnectionPool.java:188)
    at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:128)
    at org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider.getConnection(InjectedDataSourceConnectionProvider.java:47)
    at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:423)
    at org.hibernate.jdbc.ConnectionManager.getConnection(ConnectionManager.java:144)
    at org.hibernate.jdbc.AbstractBatcher.prepareQueryStatement(AbstractBatcher.java:139)
Run Code Online (Sandbox Code Playgroud)

这些似乎经常出现在Connection Closed异常之前.这些似乎是日志即将崩溃的第一个症状.

分析

按日志,我开始查看是否存在可能导致问题的任何连接池配置/ mysql配置.通过一些优秀的文章,展示了生产环境池的调整.链接12

通过这些文章,我注意到:

  1. JHanik的文章(链接1)中的下面一行提到了这一点

    将abandonWhenPercentageFull的值设置为100意味着除非我们达到maxActive限制,否则不会将连接视为放弃.

    我认为这在我的情况下可能很重要,因为我看到许多连接被抛弃了.

  2. 我的max_connections设置与推荐的设置不匹配(在链接2中)

    mysql max_connections应该等于max_active + max_idle

我做了什么

所以,根据文章的建议,我做了以下两件事:

  1. 改变放弃当PercentageFull为100
  2. 在我的MYSQL服务器中,max_connections设置为500.将其增加到600在我的连接池设置中,max_active为200,max_idle为50.将其更改为max_active = 350,max_idle = 250

这没有帮助

第二天,在高峰时段进行了以下观察:

  1. Tomcat没下来.该应用程序在高峰时段熬夜.然而,性能变得越来越差,然后应用程序几乎无法使用,即使它并没有真正下降.
  2. 数据库连接池虽然增加了大小,但已经完全利用了,我可以在一个点上看到350个与DB有效的连接.

最后,我的问题:

很明显,从应用服务器进行数据库连接的方式存在问题.所以我有两个方向来推进这个分析.

我的问题是我应该采取哪些措施?

1.问题不在于连接池设置.代码是导致问题的原因

代码中可能存在未连接数据库连接的位置.这导致大量连接被打开.

该代码使用GenericDao,它在每个Dao类中都有扩展.GenericDao使用Spring的JpaTemplate来获取EntityManager实例,该实例又用于所有数据库操作.我的理解是使用JpaTemplate处理内部关闭数据库连接的细节.

那么,我应该在哪里寻找可能的连接泄漏?

2.问题在于连接pool/mysql配置参数.但是,我提出的优化需要进一步调整

如果是,我应该查看哪些参数?我应该收集一些数据来确定我的连接池的更合适的值.(例如,对于max_active,max_idle,max_connections)


附录:完整的连接池配置

   <bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://xx.xx.xx.xx" />
        <property name="username" value="xxxx" />
        <property name="password" value="xxxx" />
        <property name="initialSize" value="10" />
        <property name="maxActive" value="350" />
        <property name="maxIdle" value="250" />
        <property name="minIdle" value="90" />
        <property name="timeBetweenEvictionRunsMillis" value="30000" />
        <property name="removeAbandoned" value="true" />
        <property name="removeAbandonedTimeout" value="60" />
        <property name="abandonWhenPercentageFull" value="100" />
        <property name="testOnBorrow" value="true" />
        <property name="validationQuery" value="SELECT 1" />
        <property name="validationInterval" value="30000" />
        <property name="logAbandoned" value="true" />
        <property name="jmxEnabled" value="true" />
    </bean>
Run Code Online (Sandbox Code Playgroud)

Rya*_* P. 10

这对于OP来说已经非常晚了,但未来可能会帮助其他人:

我在具有长期批处理作业的生产环境中遇到类似的问题.问题是如果您的代码需要的连接时间超过属性指定的时间:

name="removeAbandonedTimeout" value="60

你启用了:

<property name="removeAbandoned" value="true" />

然后它会在60秒后处理过程中断开连接.一种可能的解决方法(对我来说不起作用)是启用拦截器:

jdbcInterceptors="ResetAbandonedTimer"

这将为每次发生的读/写重置该连接的放弃计时器.不幸的是,在我的情况下,在读取/写入数据库之前,处理有时仍然需要比超时更长的时间.所以我被迫要么超过超时长度,要么禁用removeAbandonded(我选择了前一个解决方案).

希望这可以帮助别人,如果他们遇到类似的东西!


M. *_*num 0

该代码使用在每个 Dao 类中扩展的 GenericDao。GenericDao 使用 Spring 的 JpaTemplate 来获取 EntityManager 实例,该实例又用于所有数据库操作。我的理解是使用 JpaTemplate 处理内部关闭数据库连接的细节。

这可能是您问题的根源,您不应该使用 来JpaTemplate获取EntityManagerthis 会给您带来 un unmanaged EntitymanagerJpaTemplate事实上你根本不应该使用。

建议基于普通 API 编写 daos ,并像平常一样EntityManager简单地注入(使用)。EntityManager@PersistenceContext

如果您确实想使用JpaTemplateexecute方法并传入 a ,JpaCallback这将为您提供托管的EntityManager.

还要确保您正确设置了事务,而没有正确的 tx 设置连接将不会被关闭,因为 spring 不知道它应该关闭连接。