Java 多租户应用程序中的连接池。共享池与每个租户的池
我正在用Spring2.x、Hibernate5.x、SpringDataREST和MySQL5.7构建一个多租户REST服务器应用程序。 Spring2.x使用Hikari进行连接池 我将使用每个租户的数据库方法,这样每个租户都有自己的数据库 我以以下方式创建了MultiTenantConnectionProvider:Java 多租户应用程序中的连接池。共享池与每个租户的池,java,spring,amazon-web-services,amazon-rds,hikaricp,Java,Spring,Amazon Web Services,Amazon Rds,Hikaricp,我正在用Spring2.x、Hibernate5.x、SpringDataREST和MySQL5.7构建一个多租户REST服务器应用程序。 Spring2.x使用Hikari进行连接池 我将使用每个租户的数据库方法,这样每个租户都有自己的数据库 我以以下方式创建了MultiTenantConnectionProvider: @Component @Profile("prod") public class MultiTenantConnectionProviderImpl implements M
@Component
@Profile("prod")
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 3193007611085791247L;
private Logger log = LogManager.getLogger();
private Map<String, HikariDataSource> dataSourceMap = new ConcurrentHashMap<String, HikariDataSource>();
@Autowired
private TenantRestClient tenantRestClient;
@Autowired
private PasswordEncrypt passwordEncrypt;
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public Connection getAnyConnection() throws SQLException {
Connection connection = getDataSource(TenantIdResolver.TENANT_DEFAULT).getConnection();
return connection;
}
@Override
public Connection getConnection(String tenantId) throws SQLException {
Connection connection = getDataSource(tenantId).getConnection();
return connection;
}
@Override
public void releaseConnection(String tenantId, Connection connection) throws SQLException {
log.info("releaseConnection " + tenantId);
connection.close();
}
@Override
public boolean supportsAggressiveRelease() {
return false;
}
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
@Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
public HikariDataSource getDataSource(@NotNull String tentantId) throws SQLException {
if (dataSourceMap.containsKey(tentantId)) {
return dataSourceMap.get(tentantId);
} else {
HikariDataSource dataSource = createDataSource(tentantId);
dataSourceMap.put(tentantId, dataSource);
return dataSource;
}
}
public HikariDataSource createDataSource(String tenantId) throws SQLException {
log.info("Create Datasource for tenant {}", tenantId);
try {
Database database = tenantRestClient.getDatabase(tenantId);
DatabaseInstance databaseInstance = tenantRestClient.getDatabaseInstance(tenantId);
if (database != null && databaseInstance != null) {
HikariConfig hikari = new HikariConfig();
String driver = "";
String options = "";
switch (databaseInstance.getType()) {
case MYSQL:
driver = "jdbc:mysql://";
options = "?useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8&useSSL=false";
break;
default:
driver = "jdbc:mysql://";
options = "?useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8&useSSL=false";
}
hikari.setJdbcUrl(driver + databaseInstance.getHost() + ":" + databaseInstance.getPort() + "/" + database.getName() + options);
hikari.setUsername(database.getUsername());
hikari.setPassword(passwordEncrypt.decryptPassword(database.getPassword()));
// MySQL optimizations, see
// https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
hikari.addDataSourceProperty("cachePrepStmts", true);
hikari.addDataSourceProperty("prepStmtCacheSize", "250");
hikari.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
hikari.addDataSourceProperty("useServerPrepStmts", "true");
hikari.addDataSourceProperty("useLocalSessionState", "true");
hikari.addDataSourceProperty("useLocalTransactionState", "true");
hikari.addDataSourceProperty("rewriteBatchedStatements", "true");
hikari.addDataSourceProperty("cacheResultSetMetadata", "true");
hikari.addDataSourceProperty("cacheServerConfiguration", "true");
hikari.addDataSourceProperty("elideSetAutoCommits", "true");
hikari.addDataSourceProperty("maintainTimeStats", "false");
hikari.setMinimumIdle(3);
hikari.setMaximumPoolSize(5);
hikari.setIdleTimeout(30000);
hikari.setPoolName("JPAHikari_" + tenantId);
// mysql wait_timeout 600seconds
hikari.setMaxLifetime(580000);
hikari.setLeakDetectionThreshold(60 * 1000);
HikariDataSource dataSource = new HikariDataSource(hikari);
return dataSource;
} else {
throw new SQLException(String.format("DB not found for tenant %s!", tenantId));
}
} catch (Exception e) {
throw new SQLException(e.getMessage());
}
}
}
我应该在池中有2个Core*2+1=5个连接
从我得到的信息来看,这应该是池中的最大连接数,以最大限度地提高该DB实例的性能
第一个解决方案
所以我的第一个问题很简单:我如何为每个租户创建一个单独的连接池?我总共应该使用5个连接
在我看来这是不可能的。即使我为每个租户分配2个连接,我也会有200个到DBMS的连接
根据,在一个db.r4.large
实例上,我最多可以有1300个连接,因此该实例应该能够很好地承受负载。
但根据我前面提到的文章,使用数百个db连接似乎是一种不好的做法:
如果您有10000个前端用户,那么拥有10000个连接池就太疯狂了。仍然很可怕。即使是100个连接,也太过分了。您最多需要一个包含几十个连接的小池,并且希望池中的其余应用程序线程在等待连接时被阻塞
第二种解决方案
我想到的第二个解决方案是为同一DMB上的租户共享一个连接池。这意味着所有100个租户将使用相同的Hikari池(5个连接)(老实说,对我来说,这似乎很低)
这是最大限度地提高性能和缩短应用程序响应时间的正确方法吗
您对如何使用Spring、Hibernate、Mysql(托管在AWS RDS Aurora上)管理此场景有更好的了解吗?最明显的是,为每个租户打开连接是一个非常糟糕的主意。您所需要的只是一个跨所有用户共享的连接池
HikariCP pool size
中的10条连接池大小注释可能就足够了:
10是个不错的整数。看起来很低?试一试,我们敢打赌,在这样的设置下,您可以轻松处理3000名前端用户,以6000 TPS的速度运行简单查询
另请参见,这表明100个实例太多
,但这将是一个巨大的负载,需要100秒
@EssexBoy感谢您的回复。对于我的应用程序是如何制作的,我必须使用策略1。在这种情况下,您的答复仍然有效吗?@drenda它有不同的数据库,但连接池仍然不同,因此,如果有100个租户,我将至少有200个连接,最多有1000个连接到数据库。考虑到r4.1大型实例最多可以处理1300个连接,每个租户的成本似乎相当高。如果我有1000个租户,我应该如何管理这些负载?10 DB实例?你有一些有用的链接到一些真实多租户应用程序的案例研究吗?Thanks@drenda我将这个问题添加到Hikari group@drenda brettwooldridge,直接回答:谢谢你的回复。您能解释一下为什么对租户使用池连接是个坏主意吗?谢谢,因为您的连接不会被充分利用。如果您只是想一想,在使用应用程序的过程中,您访问数据库的次数是多少,那么它只会是很小的一部分。打开不必要的连接也是一个额外的负担。因此,您可能会发现,即使使用一些连接,您也可以非常顺利地运行,因为您将分发它的使用情况。例如,大多数查询只需要几毫秒到10毫秒的剩余时间,这些连接将处于空闲状态。
connections = ((core_count * 2) + effective_spindle_count)