web-dev-qa-db-ja.com

Spring、Hibernate、C3P0を使用したマルチテナントWebアプリでの接続プールの管理

マルチテナントWebアプリケーションをセットアップしようとしていますが、(理想的には)データベース分離アプローチとスキーマ分離アプローチの両方を同時に使用できます。スキーマの分離から始めますが。現在使用しているもの:

  • Spring 4.0.0
  • ハイバネート4.2.8
  • Hibernate-c3p0 4.2.8(c3p0-0.9.2.1を使用)
  • およびPostgreSQL 9.3(アーキテクチャ全体にとって本当に重要であるとは思えません)

ほとんど私は this thread に従いました(@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;
    }
}
27
Khosrow

接続ポーリングに影響を与える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:各テナントには独自のスキーマと独自の接続プールが1つのデータベースにあります

ConnectionProvider を基本クラスとして使用して AbstractMultiTenantConnectionProvider を実装することもできるため、このケースはMultiTenantConnectionProviderの実装に関する最初の戦略と非常に似ています。

この実装は、c3po構成のデータベースではなくスキーマを変更する必要があることを除いて、戦略1の推奨実装と非常に似ています。

戦略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のどちらを選択するかは、トラフィックとアプリのテナント数に依存すると思います。個別の接続プールを使用する場合:1つのテナントで使用できる接続の量ははるかに少なくなるため、正当な理由で1つのテナントが突然多くの接続を必要とする場合、この特定のテナントから見たパフォーマンスは大幅に低下します(他のテナントはそうではありません)影響を受ける)。

一方、戦略3では、何らかの正当な理由で1つのテナントが突然多くの接続を必要とする場合、すべてのテナントから見たパフォーマンスが低下します。

一般的に、戦略2の方がより柔軟で安全だと思います。すべてのテナントが特定の接続量を超えることはできません(必要に応じて、この量はテナントごとに構成できます)。

35
ben75

IMHO、接続プールの管理はデフォルトでSQL Server自体によって処理されますが、C#などの一部のプログラミング言語はプールを制御するいくつかの方法を提供しています。参照 ここ

(1)スキーマまたは(2)テナント用の個別のデータベースの選択は、テナントで予想できるデータの量によって異なります。ただし、以下の検討事項は調査する価値があります。

  1. トライアル顧客と少量顧客の共有スキーマモデルを作成します。これは、顧客のオンボーディングプロセス中にテナントに提供する機能の数で識別できます。

  2. 大規模なトランザクションデータが存在する可能性があるエンタープライズレベルの顧客を作成またはオンボードする場合は、別のデータベースを使用するのが理想的です。

  3. スキーマモデルには、SQL Serverの実装とMySQL Serverの実装が異なる場合があるため、考慮する必要があります。

  4. また、オプションを選択するときは、かなりの時間とシステムの使用後に顧客[テナント]が喜んでスケールアウトする可能性があるという事実を考慮してください。アプリでサポートされている適切なスケールアウトオプションがない場合は、煩わされる必要があります。

この議論をさらに進めるために、上記のポイントに関するコメントを共有してください

0
Saravanan