有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

多租户应用程序中的java连接池。共享池与每个租户的池

我正在用Spring 2构建一个多租户REST服务器应用程序。x、 冬眠5。x、 Spring数据REST,Mysql 5.7。 春天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名代理同时工作

考虑到这些数字,根据this article,我意识到的第一件事是,似乎很难为每个租户提供一个池

对于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的连接

根据this question,在db.r4.large实例上,我最多可以有1300个连接,因此该实例似乎应该能够很好地承受负载。 但根据我之前提到的文章,使用数百个db连接似乎是一种不好的做法:

If you have 10,000 front-end users, having a connection pool of 10,000 would be shear insanity. 1000 still horrible. Even 100 connections, overkill. You want a small pool of a few dozen connections at most, and you want the rest of the application threads blocked on the pool awaiting connections.

第二个解决方案

我想到的第二个解决方案是为同一DMB上的租户共享一个连接池。这意味着所有100个租户都将使用相同的Hikari池,共有5个连接(老实说,我觉得这似乎很低)

这是最大限度地提高性能和缩短应用程序响应时间的正确方法吗

你对如何使用Spring、Hibernate、Mysql(托管在AWS RDS Aurora上)管理这个场景有更好的了解吗


共 (1) 个答案

  1. # 1 楼答案

    最明显的是,为每个租户开放连接是一个非常糟糕的主意。你所需要的只是一个在所有用户之间共享的连接池

    1. 因此,第一步是根据一些预测找到负荷或预测负荷

    2. 决定可接受的延迟时间、突发峰值时间流量等

    3. 最后来看看你需要多少连接,并决定需要多少实例。例如,如果您的峰值时间使用率为每秒10k,并且每个查询需要10ms,那么您将需要100个打开的连接,等待1s

    4. 实现它时不需要对用户进行任何绑定。i、 e.所有人共享同一个池。除非你有一个案例可以分组,比如说高级/基本用户可以说有一套两个池等

    5. 最后,在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