Пул подключений в мультитенантном приложении.Общий пул против пула на арендатора - PullRequest
0 голосов
/ 09 октября 2018

Я создаю мультитенантное серверное приложение REST с Spring 2.x, Hibernate 5.x, Spring Data REST, Mysql 5.7.Spring 2.x использует Hikari для пула соединений.

Я собираюсь использовать БД на подход каждого арендатора , поэтому у каждого арендатора будет своя собственная база данных.

Iсоздал свой 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 арендаторов я хотел бы думать, что db.r4.large (2vcore, 15,25GB RAM и быстрый дискдоступа) с Aurora должно быть достаточно (около 150 € / месяц).

В соответствии с формулой для определения размера пула соединений:

connections = ((core_count * 2) + effective_spindle_count)

У меня должно быть 2core * 2 + 1 = 5соединений в пуле.

Из того, что я получил, это должно быть максимальное количество соединений в пуле для максимизации производительности на этом экземпляре БД.

1-е решение

Итак, мой первый вопрос довольно прост: как мне создать отдельный пул соединений для каждого арендатора, который видел, что я должен использовать всего 5 соединений?

Мне кажется, это невозможно.Даже если я назначу 2 подключения каждому арендатору, у меня будет 200 подключений к СУБД !!

Согласно этому вопросу , в случае db.r4.large у меня может быть максимум 1300 подключенийТаким образом, кажется, что экземпляр должен выдерживать нагрузку.Но согласно статье, о которой я упоминал ранее, кажется плохой практикой использовать сотни соединений с БД:

Если у вас есть 10 000 интерфейсных пользователей, наличие пула соединений в 10 000 будет безумным сдвигом.1000 все еще ужасно.Даже 100 соединений, перебор.Вам нужен небольшой пул, состоящий максимум из нескольких десятков соединений, и хотите, чтобы остальные потоки приложений были заблокированы в пуле в ожидании соединений.

2-е решение

Второе решение, которое я имею в виду, - это совместное использование пула соединений для арендаторов в одной и той же DMBS.Это означает, что все 100 арендаторов будут использовать один и тот же пул Hikari из 5 соединений (честно говоря, мне это кажется довольно низким).

Должен ли это быть верным способом максимизировать производительность и сократить время отклика приложения?

У вас есть лучшее представление о том, как управлять этим сценарием с помощью Spring, Hibernate, Mysql (размещенного на AWS RDS Aurora)?

Ответы [ 2 ]

0 голосов
/ 19 ноября 2018

Follow Предыдущие вопросы и ответы выбранная стратегия для мультитенантной среды будет (на удивление) использовать пул соединений на каждого арендатора

Стратегия 2: у каждого арендатора есть своя собственная схема и своя собственнаяпул соединений в одной базе данных

стратегия 2 более гибкая и безопасная: каждый арендатор не может использовать больше определенного количества соединений (и это количество может быть настроено для каждого арендатора, если вам это нужно)

Я предлагаю отложить формулу HikariCP здесь и использовать меньшее число арендаторов, равное 10 (динамический размер?), С небольшим размером пула соединений, равным 2.

. Больше сосредоточиться на ожидаемом трафике, обратите внимание, что10 комментария размера пула соединений в HikariCP Pool Size, возможно, должно хватить:

10 в качестве хорошего круглого числа.Кажется низким?Попробуйте, мы могли бы поспорить, что при такой настройке вы легко справитесь с 3000 интерфейсными пользователями, выполняющими простые запросы со скоростью 6000 TPS.

См. Также комментарий указывает, что 100 экземпляров - это слишком много

, но это потребует большой нагрузки, чтобы потребовать 100 с.

By @ EssexBoy

0 голосов
/ 17 ноября 2018

Совершенно определенно открытие соединения для арендатора - очень плохая идея.Все, что вам нужно, это пул соединений, общий для всех пользователей.

  1. Таким образом, первым шагом было бы найти нагрузку или предвидеть, что она будет основана на некоторых прогнозах.

  2. Определите, какая задержка является приемлемой, каков пиковый трафик при пиковой нагрузке и т. Д.

  3. В заключение определите количество соединений, которые вам понадобятся для этого, и определите количествотребуются экземпляры.Например, если пиковое время использования составляет 10 Кбит / с, а каждый запрос занимает 10 мс, вам потребуется 100 открытых соединений с задержкой 1 с.

  4. Реализуйте его без каких-либо привязок для пользователя.т.е. один и тот же пул, общий для всех.Если у вас нет случая, чтобы сгруппировать, скажем, премиум / базовые пользователи, скажем, имеют набор из двух пулов и т.д.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

...