1

我们有一个应用程序在从 db 成功执行一堆事务后遇到了这样的错误,我们意识到 jdbc 连接池充满了不会关闭并不断堆积的空闲连接。

这是来自 springboot 应用程序的错误日志:

2021-08-12T00:50:28,078 INFO  [org.apache.cxf.binding.soap.interceptor.Soap12FaultOutInterceptor:66] (http-nio-8080-exec-83) eventId=w:ef9c7df4-fae6-11eb-b2fa-000d3a434359, tenantId=, pe=, user=, app=, pid=, message=class org.apache.cxf.binding.soap.interceptor.Soap12FaultOutInterceptor$Soap12FaultOutInterceptorInternalmultipart/related; type="application/xop+xml"; boundary="uuid:c21d3fc7-b321-4895-a608-1e4e58824a90"; start="<root.message@cxf.apache.org>"; start-info="application/soap+xml"
2021-08-12 00:51:28 org.hibernate.engine.jdbc.spi.SqlExceptionHelper:137 [WARN] SQL Error: 0, SQLState: null
2021-08-12 00:51:28 org.hibernate.engine.jdbc.spi.SqlExceptionHelper:142 [ERROR] Connection pool exhausted - try increasing 'maxPoolSize' and/or 'borrowConnectionTimeout' on the DataSourceBean.
2021-08-12T00:51:28,074 WARN  [org.apache.cxf.phase.PhaseInterceptorChain:475] (http-nio-8080-exec-84) eventId=w:e29c0214-fae6-11eb-8a27-000d3a434359, tenantId=, pe=, user=, app=, pid=, message=Interceptor for {urn:ihe:iti:xds-b:2007}HIEDirectXDRService#{urn:ihe:iti:xds-b:2007}DocumentRepository_ProvideAndRegisterDocumentSet-b has thrown exception, unwinding now
org.springframework.orm.hibernate5.HibernateJdbcException: JDBC exception on Hibernate data access: SQLException for SQL [n/a]; SQL state [null]; error code [0]; Unable to acquire JDBC Connection; nested exception is org.hibernate.exception.GenericJDBCException: Unable to acquire JDBC Connection
    at org.springframework.orm.hibernate5.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:252) ~[spring-orm-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
    at org.springframework.orm.hibernate5.HibernateExceptionTranslator.convertHibernateAccessException(HibernateExceptionTranslator.java:102) ~[spring-orm-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
......
Caused by: org.hibernate.exception.GenericJDBCException: Unable to acquire JDBC Connection
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47) ~[hibernate-core-5.4.18.Final.jar!/:5.4.18.Final]
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113) ~[hibernate-core-5.4.18.Final.jar!/:5.4.18.Final]
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99) ~[hibernate-core-5.4.18.Final.jar!/:5.4.18.Final]
    at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:107) ~[hibernate-core-5.4.18.Final.jar!/:5.4.18.Final]
    at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.getPhysicalConnection(LogicalConnectionManagedImpl.java:134) ~[hibernate-core-5.4.18.Final.jar!/:5.4.18.Final]
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.connection(StatementPreparerImpl.java:50) ~[hibernate-core-5.4.18.Final.jar!/:5.4.18.Final]
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$5.doPrepare(StatementPreparerImpl.java:149) ~[hibernate-core-5.4.18.Final.jar!/:5.4.18.Final]
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:176) ~[hibernate-core-5.4.18.Final.jar!/:5.4.18.Final]
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareQueryStatement(StatementPreparerImpl.java:151) ~[hibernate-core-5.4.18.Final.jar!/:5.4.18.Final]
......
Caused by: com.atomikos.jdbc.AtomikosSQLException: Connection pool exhausted - try increasing 'maxPoolSize' and/or 'borrowConnectionTimeout' on the DataSourceBean.
    at com.atomikos.jdbc.AtomikosSQLException.throwAtomikosSQLException(AtomikosSQLException.java:29) ~[transactions-jdbc-4.0.6.jar!/:?]
    at com.atomikos.jdbc.AbstractDataSourceBean.throwAtomikosSQLException(AbstractDataSourceBean.java:76) ~[transactions-jdbc-4.0.6.jar!/:?]
    at com.atomikos.jdbc.AbstractDataSourceBean.throwAtomikosSQLException(AbstractDataSourceBean.java:71) ~[transactions-jdbc-4.0.6.jar!/:?]
    at com.atomikos.jdbc.AbstractDataSourceBean.getConnection(AbstractDataSourceBean.java:351) ~[transactions-jdbc-4.0.6.jar!/:?]
    at com.atomikos.jdbc.nonxa.AtomikosNonXADataSourceBean.getConnection(AtomikosNonXADataSourceBean.java:189) ~[transactions-jdbc-4.0.6.jar!/:?]
    at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122) ~[hibernate-core-5.4.18.Final.jar!/:5.4.18.Final]
    at org.hibernate.internal.NonContextualJdbcConnectionAccess.obtainConnection(NonContextualJdbcConnectionAccess.java:38) ~[hibernate-core-5.4.18.Final.jar!/:5.4.18.Final]
......

我们尝试增加 maxpoolsize 和 borrowConnectionTimeout 但没有成功解决问题。该应用程序使用 atomikos 作为事务管理器,我们正在尝试找到如何在没有任何活动的几分钟后关闭空闲连接并保持连接总数下降,挂出的空闲连接越少意味着有希望的活动连接越多。我们将 XA 事务与 atomikos 一起使用。请告知我们如何实现在没有活动事务的情况下保持空闲连接关闭并在一段时间后关闭或刷新空闲连接的目标,以允许更多活动连接在连接池用完的情况下启动。谢谢。顺便说一句,我们使用 atomikos 4.0.6。

application.yml 看起来像:

app:
  datasource:
    transaction:
      transaction_timeout: 600
    postgres:
      dialect: org.hibernate.dialect.PostgreSQL82Dialect
      driverClassName: org.postgresql.Driver
    tac:
      borrow_conn_timeout: 30
      max_idle_time: 300
      min_pool_size: 5
      max_pool_size: 20
      show_sql: false
      max_lifetime: 1800
      test_query: 'select 1'
hibernate:
  connection:
    release_mode: after_transaction
  current_session_context_class: jta
spring:
  profiles:
    active: PostgreSql
  jpa:
    hibernate:
      use-new-id-generator-mappings: false
    properties:
      hibernate:
        id:
          new_generator_mappings: false
  jta:
    atomikos:
      connectionfactory:
        ignore-session-transacted-flag: false
        max-pool-size: 50
        min-pool-size: 5
      datasource:
        max-pool-size: 50
        min-pool-size: 5
      properties:
        service: com.atomikos.icatch.standalone.UserTransactionServiceFactory
    enabled: true

这是数据库事务配置:

@Configuration
public class DatabaseTransactionConfiguration {

    @Value("${app.datasource.transaction.transaction_timeout}")
    Integer transactionTimeout = 300;

    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable {
        final UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(transactionTimeout);
        return userTransactionImp;
    }

    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() throws Throwable {
        final UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setTransactionTimeout(transactionTimeout);
        userTransactionManager.setStartupTransactionService(false);
        userTransactionManager.setForceShutdown(false);
        return userTransactionManager;
    }

    @Bean(name = { "transactionManager", "jtaTransactionManager" })
    @DependsOn({ "userTransaction", "atomikosTransactionManager" })
    public PlatformTransactionManager transactionManager() throws Throwable {
        final UserTransaction userTransaction = userTransaction();
        final TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
    }
}

这是 DataSourceConfigurationTAC.java:

@Configuration
@EnableTransactionManagement
public class DataSourceConfigurationTAC {

    @Value("${app.datasource.postgres.driverClassName}")
    String postgresDriverClass;

    @Value("${app.datasource.oracle.dialect}")
    String oracleDialect;

    @Value("${app.datasource.postgres.dialect}")
    String postgresDialect;
    
    @Value("${app.datasource.danc.borrow_conn_timeout}")
    Integer borrowConnectionTimeout;
    
    @Value("${app.datasource.danc.max_idle_time}")
    Integer maxIdleTime;
    
    @Value("${app.datasource.danc.min_pool_size}")
    Integer minPoolSize;
    
    @Value("${app.datasource.danc.max_pool_size}")
    Integer maxPoolSize;
    
    /**
     * Maximum amount of seconds that a connection is kept in the pool before 
     * it is destroyed automatically. Optional, defaults to 0 (no limit).
     */
    @Value("${app.datasource.tac.max_lifetime:0}")
    Integer maxLifetime;
    
    /**
     * SQL query or statement used to validate a connection before returning it. Optional.
     */
    @Value("${app.datasource.tac.test_query:@null}")
    String testQuery;

    @Bean(name = "dataSource")
    @Primary
    public DataSource tacDataSource() throws SQLException {
        AtomikosNonXADataSourceBean bean = new AtomikosNonXADataSourceBean();
        bean.setUniqueResourceName(dataSourceName);
        bean.setDriverClassName(OdxcEnvironment.isPostgreSql() ? postgresDriverClass : oracleDriverClass);
        bean.setBorrowConnectionTimeout(borrowConnectionTimeout);
        bean.setMaxIdleTime(maxIdleTime);
        bean.setMinPoolSize(minPoolSize);
        bean.setMaxPoolSize(maxPoolSize);
        bean.setMaxLifetime(maxLifetime);
        bean.setTestQuery(testQuery);
        return bean;
    }

    @Bean(name = { "sessionFactory" })
    @Primary
    public LocalSessionFactoryBean customerEntityManager(
            @Autowired @Qualifier("jtaTransactionManager") PlatformTransactionManager transactionManager,
            @Autowired @Qualifier("dataSource") DataSource tacDataSource) throws Throwable {
        LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
        sessionFactoryBean.setDataSource(tacDataSource);

        Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty("hibernate.dialect", postgresDialect);
        hibernateProperties.setProperty("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
        hibernateProperties.setProperty("javax.persistence.transactionType", "JTA");
        hibernateProperties.setProperty("hibernate.transaction.coordinator_class", "jta");
        hibernateProperties.setProperty("hibernate.jdbc.use_streams_for_binary", Boolean.FALSE.toString());
        hibernateProperties.setProperty("hibernate.id.new_generator_mappings", Boolean.FALSE.toString());
        hibernateProperties.setProperty("hibernate.show_sql", Boolean.FALSE.toString());
        hibernateProperties.setProperty("hibernate.format_sql", Boolean.FALSE.toString());
        hibernateProperties.setProperty("hibernate.use_sql_comments", Boolean.FALSE.toString());
        hibernateProperties.setProperty("hibernate.default_batch_fetch_size", "16");
        hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", Boolean.TRUE.toString());
        hibernateProperties.setProperty("hibernate.cache.region.factory_class",
                "org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory");
        hibernateProperties.setProperty("hibernate.cache.region_prefix", "connnectivity_springboot");
        hibernateProperties.setProperty("hibernate.cache.use_query_cache", Boolean.TRUE.toString());
        hibernateProperties.setProperty("hibernate.generate_statistics", Boolean.FALSE.toString());
        sessionFactoryBean.setHibernateProperties(hibernateProperties);
        sessionFactoryBean.setJtaTransactionManager(transactionManager);
        return sessionFactoryBean;
    }
}
4

0 回答 0