在我们正在开发的webapp中,我们有多个数据库(租户)。这些租户通过hibernate/hikari连接到他们自己的数据库 ConnectionProvider
. 它们是在一个自定义类中创建的 AbstractMultiTenantConnectionProvider
.
我们使用的是:spring 4.3.8、hibernate 5.0.12、hikaricp 4.0.3、tomcat 8.5/9、mysql 8。配置完全在java上完成(除了日志配置之外没有xml)。我们在一台服务器上有一个应用程序(gcp上的tomcat 9),在另一台服务器上有两个应用程序(自托管服务器上的tomcat 8.5)。gcp上的应用程序和另一个应用程序使用多租户。我也用对了 ConnectionProvider
hikaricp的github wiki中所述的驱动程序。
正如标题所示,问题在于在我们重新部署或取消部署.war文件时,连接没有关闭,这会导致连接堆积并占用资源(我们曾经出现过“连接太多”错误)。
我已经对此进行了几周的调查,可以肯定地说
没有手动打开的连接。因此,代码中不存在左开连接。
这可以通过关闭服务器、更新war并再次启动服务器来解决,但这意味着我们的一项服务不必要地停机。要改变这一点,需要对服务器进行某种程度的大修。这是我最后的办法。
降低数据库中与空闲时间相关的配置没有帮助。降低连接池中的maxlifetime也无济于事。
我从 ConnectionProvider
到 DataSources
也一样,但这并没有真正起作用。事实上,这两种情况最终都是一样的,同样的行为和一些不一致之处,比如在启动时只打开一个租户池,然后在实际使用租户时创建一个新的租户池。
唯一正确关闭的租户是“主”数据库中的租户,该数据库存储用户凭据等。这是作为bean注册的,与connectionprovider实现相比,它是唯一不同的东西。
我所尝试的:
建立习俗 stop()
方法,该方法迭代并关闭所有连接。。。但后来我发现 stop()
方法 HikariCPConnectionProvider
是一种通知方法。日志随后抛出“no-this-method”,对该方法进行了异常调用。
建立习俗 LocalContainerEntityManagerFactoryBean
用一个 @PreDestoy
方法手动关闭连接(与上面的结果相同,但对更改不起作用) DataSource
要么)。
登记每 ConnectionProvider
/ DataSource
为租户手动设置为bean。这也没用。
尝试在以下位置手动关闭连接: contextDestroyed()
正如这里和这里所建议的。
我有一种感觉,改变到 DataSource
这是一条路要走,但重新部署时有些不稳定的行为比堆叠连接更可怕。
我拒绝相信没有一个合适的方式来结束 ConnectionProvider
但是经过几周不停地阅读这篇文章,并没有真正找到一个方法来做这件事,我试着寻求帮助。
接下来,我认为最相关的代码是当前正在使用的代码(我仍然有一些以前的尝试) DataSource
和bean注册,如果这些值得一看的话)。
EntityManager工厂:
@Configuration
@ComponentScan
@EnableJpaRepositories
public class EmpresaConfiguracion {
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean empresaEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPackagesToScan("mx.com.we.tenant");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "none");
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect");
properties.put("hibernate.multi_tenant_connection_provider",multiTenantConnectionProviderImpl);
properties.put("hibernate.tenant_identifier_resolver",currentTenantIdentifierResolverImpl);
properties.put("hibernate.multiTenancy", "DATABASE");
em.setJpaPropertyMap(properties);
return em;
}
}
多租户连接提供程序:
@Service
public class MultiTenantConnectionProviderImpl extends AbstractMultiTenantConnectionProvider {
/**
*
*/
private static final long serialVersionUID = 1L;
private Map<String, ConnectionProvider> connectionProviderMap = new HashMap<>();
public MultiTenantConnectionProviderImpl() throws IOException, ClassNotFoundException {
TenanIds conexion = new TenanIds();
for (int i = 0; i < conexion.getTenants().size(); i++) {
initConnectionProviderForTenant(conexion.getTenants().get(i));
}
}
private void initConnectionProviderForTenant(String tenantId) throws IOException {
Properties properties = new Properties();
properties.put("hibernate.hikari.jdbcUrl",
"jdbc:mysql://localhost:3306/".concat(tenantId).concat(Credentials.CONNECTIONURLPARAMS.toString()));
properties.put("hibernate.hikari.dataSource.user", Credentials.USER.toString());
properties.put("hibernate.hikari.dataSource.password", Credentials.PASS.toString());
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect");
properties.put("hibernate.connection.provider_class", "org.hibernate.hikaricp.internal.HikariCPConnectionProvider");
properties.put("hibernate.hikari.maximumPoolSize", "20");
properties.put("hibernate.hikari.minimumIdle", "4");
properties.put("hibernate.hikari.maxLifetime", String.valueOf(Duration.ofMinutes(15).toMillis()));
properties.put("hibernate.hikari.poolName", tenantId.toUpperCase().concat("_POOL"));
HikariCPConnectionProvider connectionProvider = new HikariCPConnectionProvider();
connectionProvider.configure(properties);
this.connectionProviderMap.put(tenantId, connectionProvider);
}
}
tenanids类在主数据库中检查一个“clients”表,其目的是返回一个包含每个租户标识符的字符串列表。
如果您需要查看任何其他类或方法,请请求它。如果有任何写得不好的代码,也请指出。作为一名开发人员,我正在努力提高我的整体素质。
我还在下面的serverfault问题中做了一个测试,详细记录了每个想法。
暂无答案!
目前还没有任何答案,快来回答吧!