Tomcat 8 连接已被放弃
Tomcat 8 Connection has been abandoned
第一次发帖提问。谢谢大家!
我遇到的问题已经存在一段时间了,我们找不到解决方案。简而言之,应用程序构建使用 Java 8、Spring、Hibernate、PostgreSQL、JSF(此处为 PrimeFaces)、Webflow。
与连接关闭相关的问题,但应用程序似乎仍在使用它,所以下次某些逻辑借用相同的连接时,它只是 "stumbles",并抛出异常:
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,但是没有任何成功。它可能 运行 再好几天,然后由于同样的问题而失败,服务器将变得不一致,所以唯一要做的就是重新启动它。我们添加的拦截器没有帮助,只是作为尝试解决问题的一部分。
后来我们才能够可靠地重现这个问题,这个问题本身就很有趣。因此,Tomcat 会在事务中抛出任何异常时抛出 Abandoned Connection(关闭它),自定义或常规 Java,例如是否有 NoResultException 或某些基于断言的 MyCustomException 被抛出; 60 秒后 (removeAbandonedTimeout) Tomcat 将显示警告消息并且连接将关闭。但是逻辑仍然指向它的问题,并且更多的连接关闭了线路,更多的东西在执行业务逻辑时中断了。
在server.xml中配置了两个数据源(一个用于业务逻辑,另一个用于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 池,看看这是否与 Tomcat 池错误相关。但真正了解问题所在会很高兴。
您需要将 connectionTimeout
用于您的 dataSources
属性。现在您正在使用默认超时,超时后它会强制会话关闭。所以你可以尝试将下面的代码添加到 dataSources
属性 :
connectionTimeout="300000"
尝试更改您的 c3p0 属性。 unreturnedConnectionTimeout 处理挂起的连接。如果您有一些查询花费了太多时间,连接可能会超时,从而导致数据库连接挂起。你的连接有限,所以如果这个过程重复,可能会耗尽你的连接。
testConnectionOnCheckout 检查连接是否关闭,如果没有则关闭它。
c3p0.unreturnedConnectionTimeout=2000
c3p0.testConnectionOnCheckout=true
经过许多小时的调试和其他操作后解决了问题。
我应该 post 一些代码与原始问题一起被剪掉,这可能有助于其他 Whosebug 用户正确理解问题。
问题的解决方案是我们使用 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 处理出错时回滚事务。
谢谢大家关注这个问题!
第一次发帖提问。谢谢大家!
我遇到的问题已经存在一段时间了,我们找不到解决方案。简而言之,应用程序构建使用 Java 8、Spring、Hibernate、PostgreSQL、JSF(此处为 PrimeFaces)、Webflow。 与连接关闭相关的问题,但应用程序似乎仍在使用它,所以下次某些逻辑借用相同的连接时,它只是 "stumbles",并抛出异常:
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,但是没有任何成功。它可能 运行 再好几天,然后由于同样的问题而失败,服务器将变得不一致,所以唯一要做的就是重新启动它。我们添加的拦截器没有帮助,只是作为尝试解决问题的一部分。
后来我们才能够可靠地重现这个问题,这个问题本身就很有趣。因此,Tomcat 会在事务中抛出任何异常时抛出 Abandoned Connection(关闭它),自定义或常规 Java,例如是否有 NoResultException 或某些基于断言的 MyCustomException 被抛出; 60 秒后 (removeAbandonedTimeout) Tomcat 将显示警告消息并且连接将关闭。但是逻辑仍然指向它的问题,并且更多的连接关闭了线路,更多的东西在执行业务逻辑时中断了。
在server.xml中配置了两个数据源(一个用于业务逻辑,另一个用于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 池,看看这是否与 Tomcat 池错误相关。但真正了解问题所在会很高兴。
您需要将 connectionTimeout
用于您的 dataSources
属性。现在您正在使用默认超时,超时后它会强制会话关闭。所以你可以尝试将下面的代码添加到 dataSources
属性 :
connectionTimeout="300000"
尝试更改您的 c3p0 属性。 unreturnedConnectionTimeout 处理挂起的连接。如果您有一些查询花费了太多时间,连接可能会超时,从而导致数据库连接挂起。你的连接有限,所以如果这个过程重复,可能会耗尽你的连接。 testConnectionOnCheckout 检查连接是否关闭,如果没有则关闭它。
c3p0.unreturnedConnectionTimeout=2000
c3p0.testConnectionOnCheckout=true
经过许多小时的调试和其他操作后解决了问题。
我应该 post 一些代码与原始问题一起被剪掉,这可能有助于其他 Whosebug 用户正确理解问题。
问题的解决方案是我们使用 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 处理出错时回滚事务。
谢谢大家关注这个问题!