Nano Hash - криптовалюты, майнинг, программирование

Управление пулом подключений в мультитенантном веб-приложении с помощью Spring, Hibernate и C3P0

Я пытаюсь настроить многопользовательское веб-приложение с (в идеале) возможностью одновременного использования как подхода, разделенного базой данных, так и подхода, разделенного схемой. Хотя я собираюсь начать с разделения схем. В настоящее время мы используем:

  • Весна 4.0.0
  • Спящий режим 4.2.8
  • Hibernate-c3p0 4.2.8 (который использует c3p0-0.9.2.1)
  • и PostgreSQL 9.3 (я сомневаюсь, что это действительно важно для общей архитектуры)

В основном я следил за эта тема (из-за решения для @Transactional). Но я немного запутался в реализации MultiTenantContextConnectionProvider. Существует также подобный вопрос, заданный здесь, на SO, но есть некоторые аспекты, которые я не могу понять вне:

1) Что происходит с пулом соединений? Я имею в виду, им управляет Spring или Hibernate? Я предполагаю, что с ConnectionProviderBuilder - или, как было предложено - с любой его реализацией, Hibernate - это парень, который этим управляет.
2) Является ли хорошим подходом то, что Spring не управляет пулом соединений? или возможно ли, что Spring действительно управляет этим?
3) Является ли это правильным путем для будущей реализации разделения базы данных и схемы?

Любые комментарии или описания приветствуются.

application-context.xml

<beans>
    ...
    <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
        <property name="targetDataSource" ref="c3p0DataSource" />
    </bean>

    <bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="org.postgresql.Driver" />
        ... other C3P0 related config
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="packagesToScan" value="com.webapp.domain.model" />

        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
                <prop key="hibernate.default_schema">public</prop>

                <prop key="hibernate.multiTenancy">SCHEMA</prop>
                <prop key="hibernate.tenant_identifier_resolver">com.webapp.persistence.utility.CurrentTenantContextIdentifierResolver</prop>
                <prop key="hibernate.multi_tenant_connection_provider">com.webapp.persistence.utility.MultiTenantContextConnectionProvider</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="autodetectDataSource" value="false" />
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

   ...
</beans>

CurrentTenantContextIdentifierResolver.java

public class CurrentTenantContextIdentifierResolver implements CurrentTenantIdentifierResolver {
    @Override
    public String resolveCurrentTenantIdentifier() {
        return CurrentTenantIdentifier;  // e.g.: public, tid130, tid456, ...
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}

MultiTenantContextConnectionProvider.java

public class MultiTenantContextConnectionProvider extends AbstractMultiTenantConnectionProvider {
    // Do I need this and its configuratrion?
    //private C3P0ConnectionProvider connectionProvider = null;

    @Override
    public ConnectionProvider getAnyConnectionProvider() {
        // the main question is here.
    }

    @Override
    public ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
        // and of course here.
    }
}



Изменить

Что касается ответ от @ben75:

Это новая реализация MultiTenantContextConnectionProvider. Он больше не расширяет AbstractMultiTenantConnectionProvider. Он скорее реализует MultiTenantConnectionProvider, чтобы иметь возможность возвращать [Connection][4] вместо [ConnectionProvider][5]

public class MultiTenantContextConnectionProvider implements MultiTenantConnectionProvider, ServiceRegistryAwareService {
    private DataSource lazyDatasource;;

    @Override
    public void injectServices(ServiceRegistryImplementor serviceRegistry) {
        Map lSettings = serviceRegistry.getService(ConfigurationService.class).getSettings();

        lazyDatasource = (DataSource) lSettings.get( Environment.DATASOURCE );
    }

    @Override
    public Connection getAnyConnection() throws SQLException {
        return lazyDatasource.getConnection();
    }

    @Override
    public Connection getConnection(String tenantIdentifier) throws SQLException {
        final Connection connection = getAnyConnection();

        try {
            connection.createStatement().execute("SET SCHEMA '" + tenantIdentifier + "'");
        }
        catch (SQLException e) {
            throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e);
        }

        return connection;
    }
}

  • Было бы полезно знать, почему вы хотите это сделать и/или каковы ваши требования/проблемы (например, соответствие требованиям, производительность, коммерческий аргумент). В облаке в наши дни многие люди просто решают мультиарендность, загружая отдельные образы и, таким образом, имеют настоящее разделение, которое часто требуется по причинам соответствия, и поэтому вам не нужна мультиарендность на уровне DataSource. Другой вариант — просто иметь одну массивную схему, поддерживающую несколько арендаторов, и разделять/шардировать ваше приложение на основе соображений производительности. 26.01.2014
  • @AdamGent Основная причина, по которой я не собираюсь использовать отдельные экземпляры для каждого клиента, заключается в том, что мы ориентируемся на клиентов от 10 до 50 тысяч. Насколько мне известно, такое количество отдельных экземпляров обойдется намного дороже, чем их разделение с помощью мультиарендного кластера с одним экземпляром приложения с балансировкой нагрузки. Во второй части вашего комментария, ради гибкости, мы собираемся предоставить альтернативный подход к уровню данных. Если заказчику нужна отдельная база данных, и он готов платить за нее дополнительную плату в месяц, пусть будет так. 29.01.2014

Ответы:


1

Вы можете выбрать между 3 различными стратегиями, которые повлияют на опрос соединения. В любом случае вы должны предоставить реализацию MultiTenantConnectionProvider. Выбранная вами стратегия, безусловно, повлияет на вашу реализацию.

Общее замечание о MultiTenantConnectionProvider.getAnyConnection()

getAnyConnection() требуется hibernate для сбора метаданных и настройки SessionFactory. Обычно в многопользовательской архитектуре у вас есть специальная/главная база данных (или схема), не используемая ни одним арендатором. Это своего рода шаблонная база данных (или схема). Ничего страшного, если этот метод вернет соединение с этой базой данных (или схемой).

Стратегия 1: у каждого клиента есть собственная база данных (и, следовательно, собственный пул соединений).

В этом случае у каждого арендатора есть собственный пул соединений, управляемый C3PO, и вы можете предоставить реализацию MultiTenantConnectionProvider на основе AbstractMultiTenantConnectionProvider

У каждого клиента есть собственный C3P0ConnectionProvider, поэтому все, что вам нужно сделать в selectConnectionProvider(tenantIdentifier) должен вернуть правильный. Вы можете сохранить карту для их кэширования, и вы можете лениво инициализировать C3POConnectionProvider с помощью чего-то вроде:

private ConnectionProvider lazyInit(String tenantIdentifier){
    C3P0ConnectionProvider connectionProvider = new C3P0ConnectionProvider();
    connectionProvider.configure(getC3POProperties(tenantIdentifier));
    return connectionProvider;
}

private Map getC3POProperties(String tenantIdentifier){
    // here you have to get the default hibernate and c3po config properties 
    // from a file or from Spring application context (there are good chances
    // that those default  properties point to the special/master database) 
    // and alter them so that the datasource point to the tenant database
    // i.e. : change the property hibernate.connection.url 
    // (and any other tenant specific property in your architecture like :
    //     hibernate.connection.username=tenantIdentifier
    //     hibernate.connection.password=...
    //     ...) 
}

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

Этот случай очень похож на первую стратегию реализации ConnectionProvider, поскольку вы также можете использовать AbstractMultiTenantConnectionProvider в качестве базового класса для реализации вашего MultiTenantConnectionProvider

Реализация очень похожа на предлагаемую реализацию для стратегии 1, за исключением того, что вы должны изменить схему вместо базы данных в конфигурации c3po.

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

Этот случай немного отличается, поскольку каждый арендатор будет использовать одного и того же поставщика соединений (поэтому пул соединений будет общим). В случае: провайдер соединения должен установить схему для использования до любого использования соединения. т. е. вы должны реализовать MultiTenantConnectionProvider.getConnection(String tenantIdentifier) (т. е. реализация по умолчанию, предоставляемая AbstractMultiTenantConnectionProvider, не будет работать).

С postgresql вы можете сделать это с помощью:

 SET search_path to <schema_name_for_tenant>;

или используя псевдоним

 SET schema <schema_name_for_tenant>;

Итак, вот как будет выглядеть ваш getConnection(tenant_identifier);:

@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
    final Connection connection = getAnyConnection();
    try {
        connection.createStatement().execute( "SET search_path TO " + tenanantIdentifier );
    }
    catch ( SQLException e ) {
        throw new HibernateException(
                "Could not alter JDBC connection to specified schema [" +
                        tenantIdentifier + "]",
                e
        );
    }
    return connection;
}

Полезная ссылка находится здесь ( официальный документ)

Другая полезная ссылка C3POConnectionProvider.java


Вы можете комбинировать стратегию 1 и стратегию 2 в своей реализации. Вам просто нужен способ найти правильные свойства подключения/URL-адрес подключения для текущего арендатора.


ИЗМЕНИТЬ

Я думаю, что выбор между стратегией 2 или 3 зависит от трафика и количества арендаторов в вашем приложении. С отдельными пулами подключений: количество подключений, доступных для одного арендатора, будет намного меньше, и поэтому: если по какой-то законной причине одному арендатору внезапно понадобится много подключений, производительность, наблюдаемая этим конкретным арендатором, резко снизится (в то время как другой арендатор не будет повлияло).

С другой стороны, со стратегией 3, если по какой-то уважительной причине одному арендатору внезапно понадобится много подключений: производительность, наблюдаемая каждым арендатором, снизится.

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

26.01.2014
  • Спасибо @ben75. Есть ли какие-либо плюсы/минусы в стратегиях 2 и 3? 29.01.2014
  • В настоящее время я реализовал следующее: 1) Определите управляемый Spring источник данных LazyConnectionDataSourceProxy (один c3p0) через конфигурацию xml. 2) Сделать конкретную реализацию MultiTenantConnectionProvider также реализует ServiceRegistryAwareService 3) Использовать источник данных в MultiTenantConnectionProvider. он же datasource.getConnection(). Это приемлемо? 29.01.2014
  • относительно пункта 1 и 2: кажется правильным. По пункту 3: сложно что-то сказать, не видя кода. Относительно вашего первого комментария: см. мое редактирование 29.01.2014
  • +1 за четкое объяснение другой стратегии, которую вы предложили. Я отредактировал вопрос с новой реализацией MultiTenantConnectionProvider. 29.01.2014
  • Если я хочу судить о производительности моего приложения по мере увеличения числа арендаторов, есть ли способ или логика? 24.07.2020

  • 2

    ИМХО, управление пулом соединений по умолчанию будет обрабатываться самим сервером Sql, однако некоторые языки программирования, такие как C #, предлагают некоторые способы управления пулами. См. здесь

    Выбор (1) схемы или (2) отдельной базы данных для арендатора зависит от объема данных, которые вы можете ожидать для арендатора. Тем не менее, следующее соображение, возможно, стоит рассмотреть

    1. создайте общую модель схемы для пробных клиентов и клиентов с небольшим объемом, это можно определить по количеству функций, которые вы предоставляете арендатору в процессе адаптации клиента.

    2. когда вы создаете или подключаете клиента уровня предприятия, который может иметь большие транзакционные данные, идеально использовать отдельную базу данных.

    3. Модель схемы может иметь другую реализацию для SQL Server и другую для MySQL Server, что следует учитывать.

    4. Кроме того, при выборе варианта учитывайте тот факт, что клиент [арендатор] может захотеть расширить масштаб после значительного периода времени и использования системы. Если в вашем приложении не поддерживается подходящий вариант горизонтального масштабирования, вам придется побеспокоиться.

    Поделитесь своими комментариями по вышеуказанным пунктам, чтобы продолжить обсуждение.

    23.01.2014
  • Благодарю за ваш ответ. Но на самом деле я знаю плюсы и минусы отдельной схемы и/или базы данных. Во-вторых, я ищу реализацию на Java, в частности на Spring и Hibernate, а не на C#. 24.01.2014
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..