我正在使用Spring 2. x、Hibernate 5. x、Spring Data REST、Mysql 5.7构建一个多租户REST服务器应用程序。Spring 2. x使用Hikari进行连池。
我将使用每个租户数据库的方法,这样每个租户都有自己的数据库。
我以这种方式创建了我的MultiTenantConnectionProvider:
@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());
}
}
}
在我的实现中,我阅读了tenantId,并从中央管理系统获得了关于数据库实例的信息。我为每个租户创建一个新池,并缓存该池,以避免每次重新创建它。
我读了这个有趣的问题,但我的问题完全不同。我正在考虑使用AWS(同时用于服务器实例和RDS db实例)。
让我们假设一个具体的场景,其中我有100个租户。该应用程序是一个管理/销售点软件。它将仅从代理处使用。假设每个租户平均每时每刻有3名代理同时工作。
考虑到这些数字,并根据本文,我意识到的第一件事是,似乎很难为每个租户建立一个池。
对于100个租户,我想认为带有Aurora的db.r4.large(2vcore,15,25GB RAM和快速磁盘访问)应该足够了(大约150€/月)。
根据计算连接池大小的公式:
connections = ((core_count * 2) + effective_spindle_count)
我应该在池中有2个Core*2 1=5个连接。
据我所知,这应该是池中的最大连接数,以最大限度地提高数据库实例的性能。
第一种解决方案
所以我的第一个问题很简单:如何为每个租户创建一个单独的连接池,因为我总共应该只使用5个连接?
对我来说似乎不可能。即使我为每个租户分配2个连接,我也会有200个连接到DBMS!!
根据这个问题,在db上。r4。大型
实例我最多可以有1300个连接,因此该实例应该能够很好地承受负载。但根据我之前提到的文章,使用数百个db连接似乎是一种不好的做法:
如果您有10,000个前端用户,那么拥有10,000个连接池将是剪切疯狂。1000仍然很可怕。即使是100个连接,也太过分了。您最多想要一个只有几十个连接的小池子,并且您希望其余的应用程序线程被阻塞在池子上等待连接。
第二种解决方案
我想到的第二个解决方案是为同一DMB上的租户共享一个连接池。这意味着所有100个租户都将使用相同的Hikari池,共有5个连接(老实说,我觉得这似乎很低)。
这是最大限度地提高性能和缩短应用程序响应时间的正确方法吗?
你对如何使用Spring、Hibernate、Mysql(托管在AWS RDS Aurora上)管理这个场景有更好的了解吗?
遵循前面的问题
策略2:每个租户在单个数据库中都有自己的模式和自己的连接池
策略2更灵活、更安全:每个租户不能使用超过给定数量的连接(如果需要,可以为每个租户配置这个数量)
我建议把HikariCP的公式放在一边,用更少的租户数量作为10(动态大小?)低连接池大小为2。
更加关注您期望的流量,请注意HikariCP Pool Size
中的10个连接池大小注释可能就足够了:
10是个不错的整数。看起来很低?试一试,我们敢打赌,在这样的设置下,你可以轻松处理3000名前端用户,以6000 TPS的速度运行简单的查询。
另请参阅注释,说明100个实例太多
,但这必须是一个巨大的负载,需要100秒。
通过@EssexBoy
最明显的是,为每个租户打开连接是一个非常糟糕的主意。您只需要一个在所有用户之间共享的连接池。
>
因此,第一步是根据一些预测找到负荷或预测负荷。
确定可接受的延迟时间、突发峰值时间流量等
最后来看看你需要多少连接,并决定需要多少实例。例如,如果您的峰值时间使用率为每秒10k,并且每个查询需要10ms,那么您将需要100个打开的连接,等待1s。
实现它时不需要对用户进行任何绑定。i、 e.所有人共享同一个池。除非你有一个案例可以分组,比如说高级/基本用户可以说有一套两个池等等
最后,在AWS中执行此操作时,如果您需要基于第3点的多个实例,请查看是否可以根据负载自动缩放以节省成本。
看看这些比较指标
从需求激增的角度来看,这一次可能是最有趣的
https://github.com/brettwooldridge/HikariCP/blob/dev/documents/Welcome-To-The-Jungle.md
再来点...
https://github.com/brettwooldridge/HikariCP
https://www.wix.engineering/blog/how-does-hikaricp-compare-to-other-connection-pools
我们正在尝试使用nodejs/mongo原生驱动程序实现下面演示(幻灯片13-18)中概述的策略。 https://www.slideshare.net/mongodb/securing-mongodb-to-serve-an-awsbased-multitenant-securityfanatic-saas-application 总结: 从node.js.创建到mongoDB的连接池 对于租户
我正在尝试设置一个多租户web应用程序,在理想情况下,可以同时使用数据库分离和模式分离的方法。虽然我将从模式分离开始。我们目前正在使用: Spring 4.0。0 冬眠4.2。8 Hibernate-c3p04.2。8(使用c3p0-0.9.2.1) 以及PostgreSQL 9.3(我怀疑它对整个体系结构是否真的重要) 大多数时候,我遵循这个线程(因为的解决方案)。但是我有点迷失在实现中。在SO
有人知道我们是否可以增加1000s用户池的限制吗?
问题内容: 我正在尝试建立一个多租户的Web应用程序,同时(理想情况下)可以同时使用数据库分隔和模式分隔的方法。尽管我将从模式分离开始。我们目前正在使用: Spring 4.0.0 Hibernate 4.2.8 Hibernate-c3p0 4.2.8 (which uses c3p0-0.9.2.1) and PostgreSQL 9.3 (which I doubt it really ma
我们正在开发一个多租户应用程序。在体系结构方面,我们为业务逻辑设计了共享中间层,为数据持久性设计了每个租户一个数据库。也就是说,业务层将与每个租户的数据库服务器建立一组连接(连接池)。这意味着应用程序为每个租户维护单独的连接池。如果我们预计约有5000个租户,那么这个解决方案需要高资源利用率(每个租户的应用服务器和数据库服务器之间的连接),这会导致性能问题。 我们已经通过保持公共连接池解决了这个问
我正在构建一个应用程序,它应该服务于多个租户,并存储他们非常敏感的数据。每个租户都有多个用户。我的后端堆栈是用spring boot Hibernate5构建的。我希望hibernate处理多租户问题,所以我有两个问题要问专家: 如果我使用的是每租户模式范式,如何在登录时将用户名与租户关联?每个租户都有自己的用户表,那么我如何知道用户名属于哪个租户呢? 在注册时,什么是正确的方法来动态创建新模式?