wake-up-neo.com

Verwalten Sie das Verbindungspooling in einer mandantenfähigen Web-App mit Spring, Hibernate und C3P0

Ich versuche, eine Multi-Tenant-Webanwendung einzurichten, mit (idealerweise) Möglichkeit für datenbankgetrennte und schemagetrennte Verfahren gleichzeitig. Obwohl ich mit der Schematrennung beginnen werde. Wir verwenden derzeit: 

  • Frühling 4.0.0
  • Ruhezustand 4.2.8
  • Hibernate-c3p0 4.2.8 (das c3p0-0.9.2.1 verwendet)
  • und PostgreSQL 9.3 (was ich bezweifle, dass es für die Gesamtarchitektur wirklich wichtig ist)

Meistens folgte ich diesem Thread (wegen der Lösung für @Transactional). Aber ich bin etwas verloren bei der Implementierung von MultiTenantContextConnectionProvider. Es gibt auch diese ähnliche Frage hier zu SO, aber es gibt einige Aspekte, die ich nicht herausfinden kann: 

1) Was passiert mit dem Verbindungspooling? Ich meine, wird es von Spring oder Hibernate verwaltet? Ich denke, mit ConnectionProviderBuilder - oder wie vorgeschlagen - einer seiner Implementierung ist Hibernate der Mann, der es verwaltet.
2) Ist es ein guter Ansatz, dass Spring Connection Pooling nicht verwaltet? Oder ist es überhaupt möglich, dass Spring es schafft?
3) Ist dies der richtige Weg für die zukünftige Implementierung von Datenbank- und Schematrennung?

Alle Kommentare oder Beschreibungen werden absolut geschätzt.

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.
    }
}



Bearbeiten 

In Bezug auf die Antwort von @ ben75: 

Dies ist eine neue Implementierung von MultiTenantContextConnectionProvider. Es erweitert nicht mehr AbstractMultiTenantConnectionProvider. Es implementiert vielmehr MultiTenantConnectionProvider, um [Connection][4] anstelle von [ConnectionProvider][5] zurückgeben zu können.

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;
    }
}
26
Khosrow

Sie können zwischen 3 verschiedenen Strategien wählen, die sich auf die Verbindungsabfrage auswirken. In jedem Fall müssen Sie eine Implementierung von MultiTenantConnectionProvider bereitstellen. Die von Ihnen gewählte Strategie wirkt sich natürlich auf Ihre Implementierung aus.

Allgemeine Bemerkung zu MultiTenantConnectionProvider.getAnyConnection()

getAnyConnection() wird vom Ruhezustand benötigt, um Metadaten zu sammeln und die SessionFactory einzurichten. In einer mandantenfähigen Architektur haben Sie normalerweise eine spezielle/master-Datenbank (oder ein Schema), die von keinem Mandanten verwendet wird. Es ist eine Art Vorlagendatenbank (oder Schema). Es ist in Ordnung, wenn diese Methode eine Verbindung zu dieser Datenbank (oder diesem Schema) zurückgibt.

Strategie 1: Jeder Mieter hat eine eigene Datenbank. (und so ist es ein eigener Verbindungspool)

In diesem Fall verfügt jeder Mandant über einen eigenen Verbindungspool, der von C3PO verwaltet wird, und Sie können eine Implementierung von MultiTenantConnectionProvider basierend auf AbstractMultiTenantConnectionProvider bereitstellen.

Jeder Mieter hat seinen eigenen C3P0ConnectionProvider , also müssen Sie in selectConnectionProvider(tenantIdentifier) nur den richtigen zurückgeben. Sie können eine Map zum Zwischenspeichern behalten und einen C3POConnectionProvider faul initialisieren.

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

Strategie 2: Jeder Mandant hat sein eigenes Schema und seinen eigenen Verbindungspool in einer einzigen Datenbank.

Dieser Fall ist der ersten Strategie in Bezug auf die ConnectionProvider-Implementierung sehr ähnlich, da Sie auch AbstractMultiTenantConnectionProvider als Basisklasse verwenden können, um Ihre MultiTenantConnectionProvider zu implementieren.

Die Implementierung ist der vorgeschlagenen Implementierung für Strategie 1 sehr ähnlich, mit der Ausnahme, dass Sie das Schema anstelle der Datenbank in der c3po-Konfiguration ändern müssen

Strategie 3: Jeder Mandant hat sein eigenes Schema in einer einzigen Datenbank, verwendet jedoch einen gemeinsam genutzten Verbindungspool.

Dieser Fall unterscheidet sich geringfügig, da jeder Mandant denselben Verbindungsanbieter verwendet (und daher der Verbindungspool gemeinsam genutzt wird). In diesem Fall muss der Verbindungsanbieter das Schema vor der Verwendung der Verbindung festlegen. Sie müssen MultiTenantConnectionProvider.getConnection(String tenantIdentifier) implementieren (d. h. die von AbstractMultiTenantConnectionProvider bereitgestellte Standardimplementierung funktioniert nicht).

Mit postgresql können Sie dies mit:

 SET search_path to <schema_name_for_tenant>;

oder mit dem Alias

 SET schema <schema_name_for_tenant>;

So sieht Ihre getConnection(tenant_identifier); so aus:

@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;
}

Nützliche Referenz ist hier (offizielles Dokument)

Andere nützliche Links C3POConnectionProvider.Java


Sie können Strategie 1 und Strategie 2 in Ihrer Implementierung kombinieren. Sie müssen lediglich die richtigen Verbindungseigenschaften/Verbindungs-URLs für den aktuellen Mandanten finden.


EDIT

Ich denke, die Wahl zwischen Strategie 2 oder 3 hängt vom Verkehr und der Anzahl der Mieter in Ihrer App ab. Mit separaten Verbindungspools: Die Anzahl der verfügbaren Verbindungen für einen Mieter ist viel geringer. Wenn also ein Mieter plötzlich viele Verbindungen benötigt, wird die Leistung, die dieser Mieter sieht, drastisch sinken (während der andere Mieter dies nicht ist.) betroffen).

Auf der anderen Seite, mit Strategie 3, wenn ein Mieter aus einem legitimen Grund plötzlich viele Verbindungen benötigt: Die Leistung, die jeder Mieter sieht, wird abnehmen.

Im Allgemeinen denke ich, dass Strategie 2 flexibler und sicherer ist: Jeder Mieter kann nicht mehr als eine bestimmte Verbindungsmenge verbrauchen (und diese Menge kann für jeden Mieter konfiguriert werden, wenn Sie dies benötigen).

33
ben75

IMHO wird die Verbindungspoolverwaltung standardmäßig vom SQL Server selbst verwaltet. Einige Programmiersprachen wie C # bieten jedoch einige Möglichkeiten, die Pools zu steuern. hier

Die Wahl des (1) Schemas oder der (2) separaten Datenbank für einen Mandanten hängt von der Datenmenge ab, die Sie für den Mandanten erwarten können. Die folgende Überlegung kann jedoch einen Blick wert sein

  1. erstellen Sie ein gemeinsames Schemamodell für die Testkunden und die geringen -Kunden. Dies kann durch die Anzahl der -Features identifiziert werden, die Sie einem Mandanten während des Onboarding eines Kunden zur Verfügung stellen

  2. wenn Sie einen Kunden auf Unternehmensebene erstellen oder an Bord nehmen, der über große Transaktionsdaten verfügt, ist es ideal, eine separate Datenbank zu verwenden.

  3. Das Schemamodell kann eine andere Implementierung für SQL Server Und eine andere für den MySQL Server haben, was Sie berücksichtigen sollten.

  4. berücksichtigen Sie auch bei der Wahl der Option die Tatsache, dass ein Kunde [Mieter] nach einer erheblichen Zeit und Systemnutzung bereit sein möchte, seine Kapazität zu erhöhen. Wenn in Ihrer App keine geeignete Skalierungsoption unterstützt wird, müssen Sie gestört werden.

Teilen Sie Ihre Kommentare zu den oben genannten Punkten mit, um diese Diskussion weiterzuführen

0
Saravanan