带有 MultiTenantConnectionProvider 的 Springboot 多租户总是抛出 org.apache.tomcat.jdbc.pool.PoolExhaustedException:
Springboot Multi-tenant with MultiTenantConnectionProvider always throw org.apache.tomcat.jdbc.pool.PoolExhaustedException:
我已经开始转换我现有的 Spring Boot(1.5.4.RELEASE) 应用程序以使用多租户 features.it 是一个基于模式的多租户解决方案并基于 mysql。
正如下面建议的休眠文档
https://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch16.html
我已经实现了 MultiTenantConnectionProvider 和 CurrentTenantIdentifierResolver 接口并且工作正常。
package com.ifi.aws.tenant.config.hibernate;
import org.hibernate.HibernateException;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ifi.aws.tenant.entity.TenantContext;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
@Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 6246085840652870138L;
@Autowired
private DataSource dataSource;
@Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute( "USE " + tenantIdentifier );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
e
);
}
return connection;
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try {
connection.createStatement().execute( "USE " + TenantContext.DEFAULT_TENANT );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
e
);
}
connection.close();
}
@SuppressWarnings("rawtypes")
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
@Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
@Override
public boolean supportsAggressiveRelease() {
return true;
}
}
package com.ifi.aws.tenant.config.hibernate;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.springframework.context.annotation.Configuration;
import com.ifi.aws.tenant.entity.TenantContext;
@Configuration
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {
@Override
public String resolveCurrentTenantIdentifier() {
String tenantId = TenantContext.getTenantSchema();
//System.out.println("------------------ resolveCurrentTenantIdentifier = " + tenantId);
if (tenantId != null) {
return tenantId;
}
return TenantContext.DEFAULT_TENANT;
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
下面是我的休眠配置
package com.ifi.aws.tenant.config.hibernate;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.cfg.Environment;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import
org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
@Configuration
public class HibernateConfig {
@Autowired
private JpaProperties jpaProperties;
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
MultiTenantConnectionProvider multiTenantConnectionProviderImpl,
CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl) {
Map<String, Object> properties = new HashMap<>();
properties.putAll(jpaProperties.getHibernateProperties(dataSource));
properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolverImpl);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.ifi.aws");
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setJpaPropertyMap(properties);
return em;
}
}
然而,系统有时会因以下错误而崩溃
Springboot Multi-tenant with MultiTenantConnectionProvider always throw org.apache.tomcat.jdbc.pool.PoolExhaustedException: [http-nio-8086-exec-2] Timeout: Pool empty. Unable to fetch a connection in 30 seconds, none available[size:100; busy:100; idle:0; lastwait:30000].
我在这个网站上做了一些阅读,并在下面的问题中发现了完全相同的问题。
他们建议的修复方法之一是添加以下配置
spring.datasource.tomcat.max-active=100
spring.datasource.tomcat.max-idle=8
spring.datasource.tomcat.min-idle=8
但我仍然遇到同样的错误,我调试了代码,发现它在每次执行数据库调用后都会关闭连接。你们有什么想法吗?
编辑
昨天我发现 API 根本没有关闭任何连接。我写了一个简单的实用程序来检查连接状态,如下所示
@Autowired
private DataSource ds;
@Before("execution(* com.ifi.aws.*.dao.impl.springData.*.*(..))")
public void logBeforeConnection(JoinPoint jp) throws Throwable {
logDataSourceInfos("Before", jp);
}
@After("execution(* com.ifi.aws.*.dao.impl.springData.*.*(..)) ")
public void logAfterConnection(JoinPoint jp) throws Throwable {
logDataSourceInfos("After", jp);
}
public void logDataSourceInfos(final String time, final JoinPoint jp) {
final String method = String.format("%s:%s", jp.getTarget().getClass().getName(), jp.getSignature().getName());
logger.debug("--------------------------------------------------------------------------");
logger.debug(String.format("%s %s: number of connections in use by the application (active): %d.", time, method, ds.getNumActive()));
logger.debug(String.format("%s %s: the number of established but idle connections: %d.", time, method, ds.getNumIdle()));
logger.debug(String.format("%s %s: number of threads waiting for a connection: %d.", time, method, ds.getWaitCount()));
}
}
这表明活动连接在持续增长。
Before com.sun.proxy.$Proxy127:findOne: number of connections in use by the application (active): 21.
Before com.sun.proxy.$Proxy127:findOne: the number of established but idle connections: 0.
Before com.sun.proxy.$Proxy127:findOne: number of threads waiting for a connection: 0
-----------------
After com.sun.proxy.$Proxy127:findOne: number of connections in use by the application (active): 21.
After com.sun.proxy.$Proxy127:findOne: the number of established but idle connections: 0.
After com.sun.proxy.$Proxy127:findOne: number of threads waiting for a connection: 0.
o.h.e.t.i.TransactionImpl : committing
-------------------
Before com.sun.proxy.$Proxy127:findOne: number of connections in use by the application (active): 21.
Before com.sun.proxy.$Proxy127:findOne: the number of established but idle connections: 0.
Before com.sun.proxy.$Proxy127:findOne: number of threads waiting for a connection: 0
-----------------
After com.sun.proxy.$Proxy127:findOne: number of connections in use by the application (active): 22.
After com.sun.proxy.$Proxy127:findOne: the number of established but idle connections: 0.
After com.sun.proxy.$Proxy127:findOne: number of threads waiting for a connection: 0.
o.h.e.t.i.TransactionImpl : committing
-------------------
然而,它在我的本地环境中完全正常,并且它正确地关闭了连接。我的测试环境部署在 AWS t2 windows 实例中,此 API 部署为 Spring 引导 jar 文件,并在同一 t2 实例中安装了 MYSQL 服务器。我能看到的唯一区别是操作系统版本,可能是一些 MYSQL 服务器配置
编辑
我按照@xerx593
的说明解决了这个问题
问题出在 supportsAggressiveRelease = true 上,我按照@xerx593 的建议将其更改为 false。但是我仍然想知道它如何在我的本地环境而不是在测试环境中工作。根据休眠文档,它说 "Does this connection provider support aggressive release of JDBC connections and re-acquistion of those connections (if need be) later?"。测试环境和本地环境都具有相同的配置,这可能是操作系统版本或 mysql 配置的结果吗?
谢谢,
凯伦
通过“蛮力”,我们发现问题出在 supportsAggressiveRelease
标志中,设置后 (= true
),在 DEV 环境中没有问题,但会导致您的 AWS 出现问题实例。
解决方案:
@Override
public boolean supportsAggressiveRelease() {
return false;//!
}
为什么这个环境不支持“激进释放”,是在你的环境configuration/nature...
我已经开始转换我现有的 Spring Boot(1.5.4.RELEASE) 应用程序以使用多租户 features.it 是一个基于模式的多租户解决方案并基于 mysql。 正如下面建议的休眠文档
https://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch16.html
我已经实现了 MultiTenantConnectionProvider 和 CurrentTenantIdentifierResolver 接口并且工作正常。
package com.ifi.aws.tenant.config.hibernate;
import org.hibernate.HibernateException;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ifi.aws.tenant.entity.TenantContext;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
@Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 6246085840652870138L;
@Autowired
private DataSource dataSource;
@Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute( "USE " + tenantIdentifier );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
e
);
}
return connection;
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try {
connection.createStatement().execute( "USE " + TenantContext.DEFAULT_TENANT );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
e
);
}
connection.close();
}
@SuppressWarnings("rawtypes")
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
@Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
@Override
public boolean supportsAggressiveRelease() {
return true;
}
}
package com.ifi.aws.tenant.config.hibernate;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.springframework.context.annotation.Configuration;
import com.ifi.aws.tenant.entity.TenantContext;
@Configuration
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {
@Override
public String resolveCurrentTenantIdentifier() {
String tenantId = TenantContext.getTenantSchema();
//System.out.println("------------------ resolveCurrentTenantIdentifier = " + tenantId);
if (tenantId != null) {
return tenantId;
}
return TenantContext.DEFAULT_TENANT;
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
下面是我的休眠配置
package com.ifi.aws.tenant.config.hibernate;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.cfg.Environment;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import
org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
@Configuration
public class HibernateConfig {
@Autowired
private JpaProperties jpaProperties;
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
MultiTenantConnectionProvider multiTenantConnectionProviderImpl,
CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl) {
Map<String, Object> properties = new HashMap<>();
properties.putAll(jpaProperties.getHibernateProperties(dataSource));
properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolverImpl);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.ifi.aws");
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setJpaPropertyMap(properties);
return em;
}
}
然而,系统有时会因以下错误而崩溃
Springboot Multi-tenant with MultiTenantConnectionProvider always throw org.apache.tomcat.jdbc.pool.PoolExhaustedException: [http-nio-8086-exec-2] Timeout: Pool empty. Unable to fetch a connection in 30 seconds, none available[size:100; busy:100; idle:0; lastwait:30000].
我在这个网站上做了一些阅读,并在下面的问题中发现了完全相同的问题。
他们建议的修复方法之一是添加以下配置
spring.datasource.tomcat.max-active=100
spring.datasource.tomcat.max-idle=8
spring.datasource.tomcat.min-idle=8
但我仍然遇到同样的错误,我调试了代码,发现它在每次执行数据库调用后都会关闭连接。你们有什么想法吗?
编辑
昨天我发现 API 根本没有关闭任何连接。我写了一个简单的实用程序来检查连接状态,如下所示
@Autowired
private DataSource ds;
@Before("execution(* com.ifi.aws.*.dao.impl.springData.*.*(..))")
public void logBeforeConnection(JoinPoint jp) throws Throwable {
logDataSourceInfos("Before", jp);
}
@After("execution(* com.ifi.aws.*.dao.impl.springData.*.*(..)) ")
public void logAfterConnection(JoinPoint jp) throws Throwable {
logDataSourceInfos("After", jp);
}
public void logDataSourceInfos(final String time, final JoinPoint jp) {
final String method = String.format("%s:%s", jp.getTarget().getClass().getName(), jp.getSignature().getName());
logger.debug("--------------------------------------------------------------------------");
logger.debug(String.format("%s %s: number of connections in use by the application (active): %d.", time, method, ds.getNumActive()));
logger.debug(String.format("%s %s: the number of established but idle connections: %d.", time, method, ds.getNumIdle()));
logger.debug(String.format("%s %s: number of threads waiting for a connection: %d.", time, method, ds.getWaitCount()));
}
}
这表明活动连接在持续增长。
Before com.sun.proxy.$Proxy127:findOne: number of connections in use by the application (active): 21.
Before com.sun.proxy.$Proxy127:findOne: the number of established but idle connections: 0.
Before com.sun.proxy.$Proxy127:findOne: number of threads waiting for a connection: 0
-----------------
After com.sun.proxy.$Proxy127:findOne: number of connections in use by the application (active): 21.
After com.sun.proxy.$Proxy127:findOne: the number of established but idle connections: 0.
After com.sun.proxy.$Proxy127:findOne: number of threads waiting for a connection: 0.
o.h.e.t.i.TransactionImpl : committing
-------------------
Before com.sun.proxy.$Proxy127:findOne: number of connections in use by the application (active): 21.
Before com.sun.proxy.$Proxy127:findOne: the number of established but idle connections: 0.
Before com.sun.proxy.$Proxy127:findOne: number of threads waiting for a connection: 0
-----------------
After com.sun.proxy.$Proxy127:findOne: number of connections in use by the application (active): 22.
After com.sun.proxy.$Proxy127:findOne: the number of established but idle connections: 0.
After com.sun.proxy.$Proxy127:findOne: number of threads waiting for a connection: 0.
o.h.e.t.i.TransactionImpl : committing
-------------------
然而,它在我的本地环境中完全正常,并且它正确地关闭了连接。我的测试环境部署在 AWS t2 windows 实例中,此 API 部署为 Spring 引导 jar 文件,并在同一 t2 实例中安装了 MYSQL 服务器。我能看到的唯一区别是操作系统版本,可能是一些 MYSQL 服务器配置
编辑
我按照@xerx593
的说明解决了这个问题问题出在 supportsAggressiveRelease = true 上,我按照@xerx593 的建议将其更改为 false。但是我仍然想知道它如何在我的本地环境而不是在测试环境中工作。根据休眠文档,它说 "Does this connection provider support aggressive release of JDBC connections and re-acquistion of those connections (if need be) later?"。测试环境和本地环境都具有相同的配置,这可能是操作系统版本或 mysql 配置的结果吗?
谢谢, 凯伦
通过“蛮力”,我们发现问题出在 supportsAggressiveRelease
标志中,设置后 (= true
),在 DEV 环境中没有问题,但会导致您的 AWS 出现问题实例。
解决方案:
@Override
public boolean supportsAggressiveRelease() {
return false;//!
}
为什么这个环境不支持“激进释放”,是在你的环境configuration/nature...