【问题标题】:Tomcat 8 Connection has been abandonedTomcat 8 连接已被放弃
【发布时间】:2017-08-11 05:18:39
【问题描述】:

第一次发布问题。谢谢大家!

我遇到的问题已经存在了一段时间,但我们找不到解决方案。简而言之,使用 Java 8、Spring、Hibernate、PostgreSQL、JSF(此处为 PrimeFaces)、Webflow 构建应用程序。 与关闭连接相关的问题,但似乎应用程序仍在使用它,所以下次某些逻辑借用相同的连接时,它只是“绊倒”它,并抛出异常:

 Caused by: org.hibernate.exception.GenericJDBCException: could not prepare statement
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47)

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:108)

应用程序可以毫无问题地工作几天,直到发生某些事情,它会收到来自 tomcat 的连接被放弃警告。从那里将无法正常工作,并且许多进程会获得该关闭的连接并且会明显失败,因为连接已经关闭:

 WARNING [Tomcat JDBC Pool Cleaner[1989780873:1502425160484]] org.apache.tomcat.jdbc.pool.ConnectionPool.abandon Connection has been abandoned PooledConnection[org.postgresql.jdbc.PgConnection@234be71f]:java.lang.Exception
    at org.apache.tomcat.jdbc.pool.ConnectionPool.getThreadDump(ConnectionPool.java:1093)

我们的团队在这个问题上花费了数小时,在网络上搜索并咨询其他开发人员,一开始一切都归结为尝试调整 Tomcat server.xml,但没有任何成功。它可以再运行几天,然后因同样的问题而失败,服务器将变得不一致,所以唯一要做的就是重新启动它。我们添加的拦截器没有帮助,只是作为解决问题的一部分。

只有稍后我们才能可靠地复制该问题,而且该问题本身就非常有趣。因此,当在事务、自定义或常规 Java 中抛出任何异常时,Tomcat 将抛出 Abandoned Connection(关闭它),例如抛出 NoResultException 或基于断言的一些 MyCustomException; 60 秒后 (removeAbandonedTimeout) Tomcat 将显示警告消息并关闭连接。但是逻辑仍然引用它的问题,更多的连接关闭,更多的事情在执行业务逻辑时中断。

server.xml中配置了两个dataSource(一个用于业务逻辑,一个用于Jobs),如下:

<Resource auth="Container" driverClassName="org.postgresql.Driver"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" 
initialSize="5"
jdbcInterceptors=
"org.apache.tomcat.jdbc.pool.interceptor.ConnectionState; 
org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer; 
org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx (threshold=10000)"
logAbandoned="true" 
maxActive="30" 
minEvictableIdleTimeMillis="30000"
minIdle="5" 
name="jdbc/my-app-db"
password=""
removeAbandoned="true" 
removeAbandonedTimeout="60" 
testOnBorrow="true"
testOnReturn="false" 
testWhileIdle="true"
timeBetweenEvictionRunsMillis="5000" 
type="javax.sql.DataSource"
url="jdbc:postgresql://mydb.rds.amazonaws.com:5432/mydb_db" 
username=""
validationInterval="30000" 
validationQuery="SELECT 1" />

<Resource auth="Container" 
driverClassName="org.postgresql.Driver"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" 
initialSize="5"
jdbcInterceptors=
"org.apache.tomcat.jdbc.pool.interceptor.ConnectionState; 
org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer; 
org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx (threshold=10000)"
logAbandoned="true" 
maxActive="30" 
minEvictableIdleTimeMillis="30000"
minIdle="5" 
name="jdbc/my-app-db-for-jobs" 
password=""
removeAbandoned="true" 
removeAbandonedTimeout="60" 
testOnBorrow="true"
testOnReturn="false" 
testWhileIdle="true"
timeBetweenEvictionRunsMillis="5000" 
type="javax.sql.DataSource"
url="jdbc:postgresql://mydb.rds.amazonaws.com:5432/myapp_db" 
username=""
validationInterval="30000" 
validationQuery="SELECT 1" />

和xml配置:

 <bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitName" value="acme"></property>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="showSql" value="true" />
            <property name="databasePlatform" value="org.hibernate.dialect.PostgreSQLDialect" />
        </bean>
    </property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
    <property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>

和具有一些依赖关系的 POM.xml:

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.2.4.Final</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.webflow</groupId>
        <artifactId>spring-faces</artifactId>
        <version>2.4.2.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.webflow</groupId>
        <artifactId>spring-webflow</artifactId>
        <version>2.4.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>4.1.6.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.1.1</version>
    </dependency>

这是一段可能失败的代码,但实际上任何有事务的东西,或者任何查询数据库的东西都可能偶然发现关闭的池连接:

 PromoCode promoCodeEntity = null;
    try {
        JpaTransactionManager transactionManager = (JpaTransactionManager) ApplicationContextProvider
                .getApplicationContext().getBean("transactionManager");
        TransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);

        String jpql = "select p from PromoCode p where p.code = :code";
        Query query = em.createQuery(jpql).setParameter("code", promoCode);
        promoCodeEntity = (PromoCode) query.getSingleResult();

        if (promoCodeEntity.getQuantyOfUses() >= promoCodeEntity.getTotalOfUses()) {
            MyCustomException myCustomException = new MyCustomException("This promo code is not valid: [" + promoCode + "]");
            MyCustomException.setCode("PROMO_CODE");
            throw myCustomException;
        }

        if (!promoCodeEntity.getActive() || promoCodeEntity.getFinish()) {
            MyCustomException myCustomException = new MyCustomException("This promo code is not valid: [" + promoCode + "]");
            myCustomException.setCode("PROMO_CODE");
            throw myCustomException;
        }

        if (!(promoCodeEntity.getStartDateValid().before(new Date())
                && promoCodeEntity.getEndDateValid().after(new Date()))) {
            MyCustomException myCustomException = new MyCustomException("This promo code is expired: [" + promoCode + "]");
            myCustomException.setCode("PROMO_CODE");
            throw myCustomException;
        }
        promoCodeEntity.setQuantyOfUses(promoCodeEntity.getQuantyOfUses() + 1);
        transactionManager.commit(status);
    } catch (NoResultException e) {
        MyCustomException myCustomException = new MyCustomException("Promo code not found: [" + promoCode + "]");
        myCustomException.setCode("PROMO_CODE");
        throw myCustomException;
    }

有没有人遇到过这样的问题,或者我应该在哪里进一步查看?我们做了一些驱动更新(比如 postgres jdbc),目前也在评估 c3p0 Pool,看看这是否与 Tomcat Pool 的 bug 有关。但真的会很高兴了解出了什么问题。

【问题讨论】:

  • 你能解决这个问题吗?如果是,请分享如何?
  • 请看下面的回复。它可能与您的特定问题无关,但谁知道呢。谢谢

标签: java hibernate tomcat connection-pooling transactionmanager


【解决方案1】:

经过数小时的调试和其他事情,问题终于解决了。

我应该发布一些代码以及原始问题,这可能有助于其他 Stackoverflow 用户正确理解问题。

问题的解决方案是我们使用 TransactionManager 作为实例化对象并在发生异常时捕获异常

JpaTransactionManager txManager = (JpaTransactionManager) context.getBean("transactionManager");
    TransactionDefinition def = new DefaultTransactionDefinition();
    TransactionStatus status = txManager.getTransaction(def);
    try {
        String jpql = "query";
        Query query = em.createQuery(jpql).setParameter("id", id);
        User singleResult = (User) query.getSingleResult();
        singleResult.setActive(false);
        this.em.merge(singleResult);
        this.em.flush();
        txManager.commit(status);
    } catch (Exception e) {
        logger.error("lockUser(Long)", e);
        throw e;
    }
}

但是在发生异常后我们从未回滚事务后处于 catch 块中,因此 Tomcat 池管理器基本上正在关闭连接,因为它被放弃了。

在 catch 块中添加这个解决了这个特定的问题:

txManager.rollback(status);

或者我们的另一个解决方案是使用 Spring @Transactional 注释,让 spring 处理错误时回滚事务。

感谢大家关注这个问题!

【讨论】:

    【解决方案2】:

    您需要在您的dataSources 属性中使用connectionTimeout。现在您正在使用默认超时,超时后它会强制会话关闭。因此您可以尝试将以下代码添加到dataSources 属性中:

            connectionTimeout="300000"
    

    【讨论】:

      【解决方案3】:

      尝试更改您的 c3p0 属性。 unreturnedConnectionTimeout 处理挂起的连接。如果您有一些查询花费了太多时间,则连接可能会超时,从而导致数据库连接挂起。您的连接有限,因此如果此过程重复,可能会耗尽您的连接。 testConnectionOnCheckout 检查连接是否关闭,如果没有则关闭它。

      c3p0.unreturnedConnectionTimeout=2000
      c3p0.testConnectionOnCheckout=true
      

      【讨论】:

      • 感谢您的回复。该问题与查询时间过长无关,它可以,但不是现在。我们目前使用 HikariCP Pool 进行了设置,但看起来问题并没有消失。连接可能已耗尽,但不是由于查询超时原因。
      猜你喜欢
      • 1970-01-01
      • 2017-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-19
      相关资源
      最近更新 更多