【问题标题】:Manage Connection Pooling in multi-tenant web app with Spring, Hibernate and C3P0使用 Spring、Hibernate 和 C3P0 管理多租户 Web 应用程序中的连接池
【发布时间】:2014-02-09 00:37:47
【问题描述】:

我正在尝试设置一个多租户 Web 应用程序,(理想情况下)可以同时使用数据库分离和模式分离的方法。虽然我将从模式分离开始。我们目前正在使用:

  • 春季 4.0.0
  • 休眠 4.2.8
  • Hibernate-c3p0 4.2.8(使用 c3p0-0.9.2.1)
  • 和 PostgreSQL 9.3(我怀疑它对整体架构真的很重要)

我主要关注this thread(因为@Transactional 的解决方案)。但是我在实施MultiTenantContextConnectionProvider 时有点迷失了。 SO上也有this similar question在这里问过,但是有些方面我想不通:

1) 连接池会发生什么?我的意思是,它是由 Spring 还是 Hibernate 管理的?我猜想ConnectionProviderBuilder - 或建议 - 它的任何实现,Hibernate 都是管理它的人。
2)Spring不管理连接池是一种好方法吗?或者 Spring 是否有可能管理它?
3)这是未来实现数据库和模式分离的正确途径吗?

任何 cmets 或描述都非常感谢。

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的the answer

这是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 级别的多租户。另一种选择是拥有一个能够感知多租户并根据性能原因对您的应用程序进行分区/分片的大型架构。
  • @AdamGent 我不打算为每个客户使用单独的实例的主要原因是我们的目标是 10K-50K+ 客户。据我所知,拥有这么多“单独的实例”比通过单实例应用程序的负载平衡、多租户集群拆分它们的成本要高得多。对于您评论的第二部分,为了灵活起见,我们将为数据层提供替代方法。如果客户想要一个单独的数据库,并且他们愿意为它支付额外的费用/月,那就这样吧。

标签: spring hibernate postgresql multi-tenant c3p0


【解决方案1】:

恕我直言,连接池管理将默认由 Sql Server 本身处理,但是某些编程语言(如 C#)确实提供了一些控制池的方法。参考here

为租户选择 (1) 架构或 (2) 单独数据库取决于您可以为租户预期的数据量。但是,以下考虑值得研究

  1. 为试用客户和低端客户创建共享模式模型 批量客户,这可以通过数量来识别 您在处理过程中向租户提供的功能 引导客户

  2. 当您创建或加入企业级客户时,可能 有大量的事务数据,最好单独去 数据库。

  3. 架构模型可能对 SQL Server 有不同的实现 和一个不同的 MySQL 服务器,你应该考虑。

  4. 在选择该选项时,请务必考虑客户 [租户] 在经过相当长的时间和系统使用后可能愿意横向扩展这一事实。如果您的应用不支持适当的横向扩展选项,您将不得不为此烦恼。

就以上几点分享您的 cmets,以便进一步讨论

【讨论】:

  • 感谢您的回复。但实际上我知道单独的架构和/或数据库的优缺点。其次,我正在寻找 Java 的实现,特别是 Spring 和 Hibernate,而不是 C#。
【解决方案2】:

您可以在 3 种会影响连接轮询的不同策略之间进行选择。在任何情况下,您都必须提供MultiTenantConnectionProvider 的实现。您选择的策略当然会影响您的实施。

关于MultiTenantConnectionProvider.getAnyConnection()的一般说明

getAnyConnection() 是 hibernate 收集元数据和设置 SessionFactory 所必需的。通常在多租户架构中,您有一个未被任何租户使用的特殊/主数据库(或模式)。它是一种模板数据库(或模式)。如果这个方法返回一个到这个数据库(或模式)的连接就可以了。

策略 1:每个租户都有自己的数据库。(因此有自己的连接池)

在这种情况下,每个租户都有自己的由 C3PO 管理的连接池,您可以基于AbstractMultiTenantConnectionProvider 提供MultiTenantConnectionProvider 的实现

每个租户都有自己的C3P0ConnectionProvider,因此您在selectConnectionProvider(tenantIdentifier) 中要做的就是返回正确的。您可以保留一个 Map 来缓存它们,您可以使用以下内容延迟初始化 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;
}

有用的参考是here(官方文档)

其他有用的链接C3POConnectionProvider.java


您可以在实施中结合使用策略 1 和策略 2。您只需要一种方法来为当前租户找到正确的连接属性/连接 url。


编辑

我认为策略 2 或 3 之间的选择取决于您应用的流量和租户数量。使用单独的连接池:一个租户可用的连接数量会少得多,因此:如果出于某种正当原因,一个租户突然需要很多连接,则该特定租户看到的性能将急剧下降(而另一个租户不会受影响)。

另一方面,对于策略 3,如果出于某种正当原因,一个租户突然需要很多连接:每个租户看到的性能都会下降。

总的来说,我认为策略2更加灵活和安全:每个租户不能消耗超过给定数量的连接(如果需要,可以为每个租户配置这个数量)

【讨论】:

  • 谢谢@ben75。策略 2 和 3 有什么优缺点吗?
  • 目前我实现如下:1)通过xml配置定义Spring管理的LazyConnectionDataSourceProxy数据源(c3p0之一)。 2)具体实现MultiTenantConnectionProvider也实现ServiceRegistryAwareService 3)在MultiTenantConnectionProvider中使用数据源。又名datasource.getConnection()。这符合条件吗?
  • 关于第 1 点和第 2 点:这似乎是正确的。关于第3点:不看代码很难说什么。关于您的第一条评论:查看我的编辑
  • +1 用于清楚地解释您建议的不同策略。我用 MultiTenantConnectionProvider 的新实现编辑了这个问题。
  • 如果我想随着租户数量的增加来判断我的应用程序的性能,有什么方法或逻辑吗?
猜你喜欢
  • 2019-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
  • 2019-06-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多