Zarządzaj Poolingiem połączeń w aplikacji internetowej dla wielu dzierżawców za pomocą Spring, Hibernate i C3P0

Próbuję skonfigurować aplikację internetową z wieloma dzierżawcami, z (najlepiej) możliwością zarówno oddzielania bazy danych, jak i oddzielania schematów w tym samym czasie. Chociaż zacznę od rozdzielenia schematów. Obecnie używamy:

  • Spring 4.0.0
  • Hibernate 4.2.8
  • Hibernate-c3p0 4.2.8 (który używa c3p0-0.9.2.1)
  • PostgreSQL 9.3 (w co wątpię, żeby miało to znaczenie dla całej architektury)]}

Głównie podążałem za ten wątek (ze względu na rozwiązanie dla @Transactional). Ale jestem trochę zagubiony w realizacji MultiTenantContextConnectionProvider. Jest też to podobne pytanie zadane tutaj na SO, ale są pewne aspekty, których nie mogę rozgryźć:

1) co się dzieje z Poolingiem połączeń? Czy poradzi sobie wiosna czy Hibernacja? Domyślam się, że z ConnectionProviderBuilder - lub zgodnie z sugestią - którąkolwiek z jego implementacji, Hibernate jest facetem, który nim zarządza.
2) czy to dobre podejście, że Spring nie zarządza połączeniem / Align = "left" / a może Spring sobie z tym poradzi?
3) czy jest to właściwa ścieżka do przyszłego wdrożenia zarówno separacji bazy danych,jak i schematu?

Wszelkie komentarze lub opisy są mile widziane.

Zastosowanie-kontekst.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>

/ align = "left" / 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.
    }
}



Edit

Odnośnie odpowiedzi @ben75:

Jest to nowa implementacja MultiTenantContextConnectionProvider. Nie rozciąga się już AbstractMultiTenantConnectionProvider. To raczej implementuje MultiTenantConnectionProvider, aby móc zwrócić [Connection][4] zamiast [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;
    }
}
Author: Community, 2014-01-20

2 answers

Możesz wybrać jedną z 3 różnych strategii, które będą miały wpływ na ankiety połączenia. W każdym przypadku musisz zapewnić realizację MultiTenantConnectionProvider. Strategia, którą wybierzesz, będzie oczywiście miała wpływ na twoje wdrożenie.

Ogólna uwaga o MultiTenantConnectionProvider.getAnyConnection()

getAnyConnection() jest wymagany przez hibernate do zbierania metadanych i konfiguracji SessionFactory. Zazwyczaj w architekturze wielu dzierżawców istnieje specjalna / główna baza danych (lub schemat), która nie jest używana przez żadnego dzierżawcę. Jest to rodzaj bazy szablonów (lub schematu). Jest ok, jeśli ta metoda zwróci połączenie z tą bazą danych (lub schematem).

Strategia 1: każdy najemca ma własną bazę danych. (a więc jest to własna Pula połączeń)

W tym przypadku każdy najemca ma własną pulę połączeń zarządzaną przez C3PO i możesz zapewnić wdrożenie MultiTenantConnectionProvider na podstawie AbstractMultiTenantConnectionProvider

Każdy najemca ma swoje C3P0ConnectionProvider, więc wszystko, co musisz zrobić w selectConnectionProvider(tenantIdentifier) to zwrócenie WŁAŚCIWEGO. Można zachować mapę do buforowania ich i można leniwie zainicjalizować C3POConnectionProvider z czymś takim jak:

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=...
    //     ...) 
}

Strategia 2: każdy dzierżawca ma własny schemat i własną pulę połączeń w jednej bazie danych

Ten przypadek jest bardzo podobny do pierwszej strategii dotyczącej implementacji ConnectionProvider, ponieważ można również użyć AbstractMultiTenantConnectionProvider jako klasa bazowa do implementacji twojego MultiTenantConnectionProvider

The implementacja jest bardzo podobna do sugerowanej implementacji dla strategii 1 z tym wyjątkiem, że musisz zmienić schemat zamiast bazy danych w konfiguracji c3po

Strategia 3: każdy dzierżawca ma własny schemat w jednej bazie danych, ale korzysta ze współdzielonej puli połączeń

Ten przypadek jest nieco inny, ponieważ każdy dzierżawca będzie używał tego samego dostawcy połączenia (a więc pula połączeń będzie współdzielona). W przypadku: dostawca połączenia musi ustawić schemat na używać przed jakimkolwiek użyciem połączenia. tzn. musisz zaimplementować MultiTenantConnectionProvider.getConnection(String tenantIdentifier) (tzn. Domyślna implementacja dostarczona przez AbstractMultiTenantConnectionProvider nie będzie działać).

Z postgresql możesz to zrobić za pomocą:

 SET search_path to <schema_name_for_tenant>;

Lub używając aliasu

 SET schema <schema_name_for_tenant>;

Oto jak będzie wyglądał twój 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;
}

Przydatne odniesienie jest tutaj (official doc)

Inny przydatny link C3POConnectionProvider.java


Możesz połączyć strategię 1 i strategia 2 w Twojej realizacji. Potrzebujesz tylko sposobu, aby znaleźć właściwe właściwości połączenia/adres URL połączenia dla bieżącego dzierżawcy.


EDIT

Myślę, że wybór pomiędzy strategią 2 lub 3 zależy od ruchu i liczby najemców w Twojej aplikacji. Z oddzielnymi pulami połączeń: ilość połączeń dostępnych dla jednego najemcy będzie znacznie niższa i tak: jeśli z jakiegoś powodu jeden najemca potrzebuje nagle wielu połączeń wydajność widziana przez ten konkretny najemca drastycznie spadnie(podczas gdy inny najemca nie będzie miał wpływu).

Z drugiej strony, w strategii 3, Jeśli z jakiegoś uzasadnionego powodu jeden najemca potrzebuje nagle wielu połączeń: wydajność widziana przez każdego najemcę spadnie.

Ogólnie rzecz biorąc, uważam, że strategia 2 jest bardziej elastyczna i bezpieczna : każdy najemca nie może zużywać więcej niż określona ilość połączenia (i ta kwota może być skonfigurowana dla każdego najemcy, jeśli tego potrzebujesz) [20]}
 32
Author: ben75,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-07 21:27:11

IMHO, zarządzanie pulą połączeń będzie domyślnie obsługiwane przez sam serwer Sql, jednak niektóre języki programowania, takie jak C#, oferują pewne sposoby kontroli puli. Zobacz tutaj

Wybór (1) schematu lub (2) oddzielnej bazy danych dla dzierżawcy zależy od ilości danych, które można przewidzieć dla dzierżawcy. Warto jednak przyjrzeć się następującej kwestii

  1. Tworzenie współdzielonego modelu schematu dla klientów próbnych i niski klientów wolumenowych, można to rozpoznać po liczbie funkcje, które udostępniasz najemcy podczas procesu wprowadzenie klienta

  2. Gdy tworzysz lub wdrażasz klienta na poziomie przedsiębiorstwa, który może mają duże dane transakcyjne, idealnie nadaje się do oddzielnego baza danych.

  3. Model schematu może mieć inną implementację dla SQL Server i inny dla serwera MySQL, który należy wziąć pod uwagę.

  4. Również wtedy, gdy wybierając tę opcję, należy wziąć pod uwagę fakt ,że klient [najemca] może być skłonny do skalowania się po znacznej ilości czasu i użytkowania systemu. Jeśli w Twojej aplikacji nie ma odpowiedniej opcji skalowania, musisz się martwić.

Podziel się swoimi uwagami na temat powyższych punktów, aby kontynuować tę dyskusję

 0
Author: Saravanan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-10 12:31:42