dre*_*nda 10 java spring amazon-web-services amazon-rds hikaricp
我正在使用 Spring 2.x、Hibernate 5.x、Spring Data REST、Mysql 5.7 构建多租户 REST 服务器应用程序。\nSpring 2.x 使用 Hikari 进行连接池。
\n\n我将使用每个租户的数据库方法,因此每个租户都将拥有自己的数据库。
\n\n我以这种方式创建了我的 MultiTenantConnectionProvider:
\n\n@Component\n@Profile("prod")\npublic class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {\n private static final long serialVersionUID = 3193007611085791247L;\n private Logger log = LogManager.getLogger();\n\n private Map<String, HikariDataSource> dataSourceMap = new ConcurrentHashMap<String, HikariDataSource>();\n\n @Autowired\n private TenantRestClient tenantRestClient;\n\n @Autowired\n private PasswordEncrypt passwordEncrypt;\n\n @Override\n public void releaseAnyConnection(Connection connection) throws SQLException {\n connection.close();\n }\n\n @Override\n public Connection getAnyConnection() throws SQLException {\n Connection connection = getDataSource(TenantIdResolver.TENANT_DEFAULT).getConnection();\n return connection;\n\n }\n\n @Override\n public Connection getConnection(String tenantId) throws SQLException {\n Connection connection = getDataSource(tenantId).getConnection();\n return connection;\n }\n\n @Override\n public void releaseConnection(String tenantId, Connection connection) throws SQLException {\n log.info("releaseConnection " + tenantId);\n connection.close();\n }\n\n @Override\n public boolean supportsAggressiveRelease() {\n return false;\n }\n\n @Override\n public boolean isUnwrappableAs(Class unwrapType) {\n return false;\n }\n\n @Override\n public <T> T unwrap(Class<T> unwrapType) {\n return null;\n }\n\n public HikariDataSource getDataSource(@NotNull String tentantId) throws SQLException {\n if (dataSourceMap.containsKey(tentantId)) {\n return dataSourceMap.get(tentantId);\n } else {\n HikariDataSource dataSource = createDataSource(tentantId);\n dataSourceMap.put(tentantId, dataSource);\n return dataSource;\n }\n }\n\n public HikariDataSource createDataSource(String tenantId) throws SQLException {\n log.info("Create Datasource for tenant {}", tenantId);\n try {\n Database database = tenantRestClient.getDatabase(tenantId);\n DatabaseInstance databaseInstance = tenantRestClient.getDatabaseInstance(tenantId);\n if (database != null && databaseInstance != null) {\n HikariConfig hikari = new HikariConfig();\n String driver = "";\n String options = "";\n switch (databaseInstance.getType()) {\n case MYSQL:\n driver = "jdbc:mysql://";\n options = "?useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8&useSSL=false";\n break;\n\n default:\n driver = "jdbc:mysql://";\n options = "?useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8&useSSL=false";\n }\n\n hikari.setJdbcUrl(driver + databaseInstance.getHost() + ":" + databaseInstance.getPort() + "/" + database.getName() + options);\n hikari.setUsername(database.getUsername());\n hikari.setPassword(passwordEncrypt.decryptPassword(database.getPassword()));\n\n // MySQL optimizations, see\n // https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration\n hikari.addDataSourceProperty("cachePrepStmts", true);\n hikari.addDataSourceProperty("prepStmtCacheSize", "250");\n hikari.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");\n hikari.addDataSourceProperty("useServerPrepStmts", "true");\n hikari.addDataSourceProperty("useLocalSessionState", "true");\n hikari.addDataSourceProperty("useLocalTransactionState", "true");\n hikari.addDataSourceProperty("rewriteBatchedStatements", "true");\n hikari.addDataSourceProperty("cacheResultSetMetadata", "true");\n hikari.addDataSourceProperty("cacheServerConfiguration", "true");\n hikari.addDataSourceProperty("elideSetAutoCommits", "true");\n hikari.addDataSourceProperty("maintainTimeStats", "false");\n hikari.setMinimumIdle(3);\n hikari.setMaximumPoolSize(5);\n\n hikari.setIdleTimeout(30000);\n hikari.setPoolName("JPAHikari_" + tenantId);\n // mysql wait_timeout 600seconds\n hikari.setMaxLifetime(580000);\n hikari.setLeakDetectionThreshold(60 * 1000);\n\n HikariDataSource dataSource = new HikariDataSource(hikari);\n\n\n return dataSource;\n\n } else {\n throw new SQLException(String.format("DB not found for tenant %s!", tenantId));\n }\n } catch (Exception e) {\n throw new SQLException(e.getMessage());\n }\n }\n\n}\nRun Code Online (Sandbox Code Playgroud)\n\n在我的实现中,我读取tenantId,并从中央管理系统获取有关数据库实例的信息。\n我为每个租户创建一个新池,并缓存该池以避免每次都重新创建它。
\n\n我读到了这个有趣的问题,但我的问题完全不同。\n我正在考虑使用 AWS(既用于服务器实例,又用于 RDS 数据库实例)。
\n\n让我们假设一个具体场景,其中我有 100 个租户。\n该应用程序是一个管理/销售点软件。它将仅由代理使用。假设每个租户平均每个时刻有 3 个代理同时工作。
\n\n考虑到这些数字并根据本文,我意识到的第一件事是似乎很难为每个租户提供一个游泳池。。
\n\n对于 100 个租户,我认为带有 Aurora 的db.r4.large(2vcore、15,25GB RAM 和快速磁盘访问)应该足够了(大约 150\xe2\x82\xac/月)。
\n\n根据公式来确定连接池的大小:
\n\nconnections = ((core_count * 2) + effective_spindle_count)\nRun Code Online (Sandbox Code Playgroud)\n\n我应该在池中有 2core*2 + 1 = 5 个连接。
\n\n根据我得到的信息,这应该是池中的最大连接数,以最大限度地提高该数据库实例的性能。
\n\n第一个解决方案
\n\n所以我的第一个问题非常简单:如何为每个租户创建一个单独的连接池,我总共应该只使用 5 个连接?
\n\n对我来说这似乎不可能。即使我为每个租户分配 2 个连接,我也会有 200 个与 DBMS 的连接!
\n\n根据这个问题,在一个db.r4.large实例上,我最多可以拥有 1300 个连接,因此该实例似乎应该很好地应对负载。\n但是根据我之前提到的文章,使用数百个到数据库的连接似乎是一种不好的做法:
\n\n\n如果您有 10,000 个前端用户,那么拥有 10,000 个连接池将是疯狂的。1000还是太恐怖了 即使是 100 个连接,也太过分了。您需要一个最多包含几十个连接的小型池,并且希望其余应用程序线程阻塞在池中等待连接。
\n
第二种解决方案
\n\n我想到的第二个解决方案是为同一 DMBS 上的租户共享连接池。这意味着所有 100 个租户将使用同一个包含 5 个连接的 Hikari 池(老实说,对我来说这似乎很低)。
\n\n这是否是最大化性能并减少应用程序响应时间的正确方法?
\n\n您是否更清楚如何使用 Spring、Hibernate、Mysql(托管在 AWS RDS Aurora 上)管理此场景?
\n毫无疑问,为每个租户打开连接是一个非常糟糕的主意。您所需要的只是一个在所有用户之间共享的连接池。
因此,第一步是找到负载或根据某些预测来预测负载。
确定可接受的延迟时间、突发峰值时间流量是多少等
最后确定所需的连接数并决定所需的实例数。例如,如果您的峰值时间使用率为每秒 10k,并且每个查询需要 10 毫秒,那么您将需要 100 个打开的连接来实现 1 秒的延迟。
实现它时无需对用户进行任何绑定。即所有人共享同一个池。除非你有一个案例来分组高级/基本用户说有一组两个池等
最后,当您在 AWS 中执行此操作时,如果您根据第 3 点需要超过 1 个实例,请查看是否可以根据负载自动向上/向下扩展以节省成本。
查看这些以获取一些比较指标
就峰值需求而言,这可能是最有趣的
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
| 归档时间: |
|
| 查看次数: |
6398 次 |
| 最近记录: |