·天新网首页·加入收藏·设为首页·网站导航
数码笔记本手机摄像机相机MP3MP4GPS
硬件台式机网络服务器主板CPU硬盘显卡
办公投影打印传真
家电电视影院空调
游戏网游单机动漫
汽车新车购车试驾
下载驱动源码
学院开发设计
考试公务员高考考研
业界互联网通信探索
针对 SaaS 应用程序的基于 Hibernate 框架的数据库分片
http://www.21tx.com 2010年11月04日 ibm Arun Viswanathan

  针对 SaaS 应用程序的基于 Hibernate 框架的数据库分片

  SaaS 正在改变设计、构建、部署和操作应用程序的方式。开发一个 SaaS 应用程序与开发一个通用企业应用程序之间的关键区别在于,SaaS 应用程序必须是多租户的。其他关键 SaaS 需求,比如安全性、定制、面向服务的架构(SOA)和集成,也影响 SaaS 应用程序架构。

  多租户是指应用程序在单一代码库内寄存多个承租者并共享数据库等架构的能力。有多种设计选项可启用多租户数据架构 — 每个承租者的专用数据库、每个承租者的共享数据库独立模式,以及所有承租者的碎片数据库共享模式。一种经济高效的策略是,为所有承租者采用一种共享数据库和共享模式策略。采用这种方法会对数据库伸缩带来重大挑战,因为数据库在 SaaS 应用程序支持的所有承租者之间共享。一种简单的方法是基于承租者 ID、通过数据库分片横向扩展共享数据库。

  数据库分片 是指对数据库进行水平分区,其中每个分区称为一个碎片。有一些开源以及专用软件,比如 Hibernate Shards、Apache Slice、SQLAlchemy ORM 和 dbShards,可提供分片功能。本文将 Hibernate 作为 J2EE 空间中最常用的 ORM 框架。我们将使用 Hibernate Shards(Hibernate 的一个扩展)解决 SaaS 应用程序的数据库扩展问题,并向 Hibernate 用户提供分片的功能。

  SaaS 应用程序的数据库分片

  SaaS 应用程序采用一种共享数据库,共享模式的方法,其设计考虑因素在于,当它不能再满足基本性能指标时就需要予以伸缩 — 例如,当太多用户尝试同时访问数据库时,或数据库的大小会导致查询和更新的执行时间太长。横向扩展共享数据库的一种方式是数据库分片。这是横向扩展数据库最有效的方式,因为根据承租者 ID,一个共享模式中的行对于不同承租者是不同的。可以基于承租者 ID 轻松地对数据库进行水平分区。从而便于将属于每个承租者的数据移动到单个分区。

  在对多租户 SaaS 应用程序进行数据分区时,我们需要考虑这样的因素,比如因并发用户数量的增多而导致性能降低,或因多个承租者的配置而增加数据库大小,从而影响现有数据库的性能特征。这将有助于您根据单个承租者的数据库大小需求或同时访问数据库的单个承租者的用户数来选择合适的分区技术。数据库分片提供许多优势,比如对数据库的更快读写、改进的搜索响应、更小的表格尺寸以及对表格的按需分配。

  Hibernate 数据库分片可用于构建采用其他多租户数据架构选项的 SaaS 应用程序,比如每个承租者的专用数据库,或每个承租者的共享数据库独立模式。Hibernate 数据库分片可以基于承租环境将连接到多个数据库的应用程序代码抽取出来,方法就是提供元数据配置并进行 API 分区,即根据承租环境处理插入数据的操作和从多个数据库中读取数据的操作。

  使用 Hibernate Shards 实现分区

  Hibernate Shards 是一个旨在通过向 Hibernate Core 添加水平分区支持来封装和最小化这种复杂性的框架。它最小化处理碎片数据的实现复杂性。Hibernate Shards 的主要目标是让应用程序使用标准 Hibernate Core API 查询和办理共享数据集。该方法具有以下优势:

  提供非侵入性解决方案来支持数据库分片,同时使用 Hibernate 构建现有 SaaS 应用程序。

  允许使用 Hibernate、但尚不需要分片的 SaaS 应用程序采用解决方案,而无需重大重构,前提是它们确实达到了这个阶段。

  Hibernate Shards 提供:

  Hibernate Core 提供碎片感知的界面扩展,因此代码不需要知道它是在与一个分片数据集交互。充当分片引擎的碎片感知扩展如下所示:

  org.hibernate.shards.ShardedSessionFactory

  org.hibernate.shards.criteria.ShardedCriteria

  org.hibernate.shards.session.ShardedSession

  org.hibernate.shards.query.ShardedQuery

  实现特定于应用程序的分片策略的界面。上面描述的分片引擎使用应用程序提供的分片策略。

  org.hibernate.shards.strategy.Access.ShardAccessStrategy — 通过该策略,Hibernate 决定如何跨多个碎片应用数据库操作。每执行一次查询,都会引用该策略。两个默认实现 SequentialShardAccessStrategy 和 ParallelShardAccessStrategy 已经提供。

  org.hibernate.shards.strategy.resolution.ShardResolutionStrategy — 该策略用于决定给定 ID 的对象所在的碎片集。碎片解析与 ID 生成相联,Hibernate 为 ID 生成提供多种策略,比如本地、应用程序级 UUID 生成和分布式 hilo 生成。

  org.hibernate.shards.strategy.selection.ShardSelectionStrategy — 该策略允许我们决定创建新对象所在的碎片。基于应用程序的需求,我们决定该界面的实现。尽管 Hibernate 最初提供一个开箱即用的循环实现,我们需要为基于 SaaS 的应用程序提供基于承租者 ID 的实现。

  对虚拟碎片的支持,用于缓和与重新分片相关的重负。

  设计 SaaS 应用程序时应考虑到,随着有越来越多的新承租者配置给应用程序,它们在将来可能需要扩展一个数据库。在设计时,您可能认为两个数据库就足够了,但当一个 SaaS 应用程序的数据集超过了最初分配给应用程序的数据库容量时,添加数据库就很有必要。通常理想的解决方案是跨多个碎片重新分布数据。重新分片是个很复杂的问题,设计过程中如不加以考虑,可能会在管理生产应用程序时造成严重的并发状况。

  使用虚拟碎片,我们可以在一个 hibernate 配置文件中将多个虚拟碎片指向物理碎片。这将允许我们定义多个虚拟碎片,将其映射到两三个物理碎片。随着项目的扩展,可添加两个物理碎片来更改配置。

  使用 Hibernate Framework for Persistence 设计和实现 SaaS 应用程序时,我们应当为每个承租者创建可映射到一个预期物理数据库的虚拟碎片。提供新承租者之后,我们可以再创建一个虚拟碎片并将其映射到同一预期物理碎片或更多物理碎片。

  使用 Hibernate Shards 构建一个多租户电子商务应用程序

  让我们看一下如何利用这个概念,同时构建一个多租户电子商务应用程序。假定多租户电子商务应用程序在使用共享模式和共享数据库策略解决多承租数据需求。随数据量的增多,我们需要扩展数据库以处理新的承租者。在本例中,我们有两个共享单一数据库的承租者,且当有其他承租者进入时,其数据基于承租者 ID 被移动到不同的数据库碎片。该应用程序还使用虚拟碎片选项处理将来的数据库可伸缩性需求。该应用程序在 Customer 表格中为分布于多个地理位置的承租者提供客户配置信息。

  设置和配置

  先决条件

  以下是用于测试该样例的主要库和工具。

  hibernate-shards.jar — V3.2.x

  hibernate3.jar — V3.0.0.Beta2

  hsqldb.jar — V1.8.0

  Eclipse IDE — V3.5(样例应当原封不动地适用于早期版本)

  Customer 表的数据库模式

表 1. Customer 表

Column name Column type Remarks
CUSTOMER_ID DECIMAL(40,0) PRIMARY KEY
TENANT_ID VARCHAR(50)  
FIRST_NAME VARCHAR(50)  
LAST_NAME VARCHAR(50)  
LOCATION VARCHAR(50)  
UPDATE_TIME TIMESTAMP  

  一个与表格对应的 Java™ 对象模型被创建。相应的代码片段如下所示。

清单 1. Customer 对象模型的样例代码

  public class Customer { 
  private BigInteger customerId; 
  private String tenantId; 
  private String firstName; 
  private String lastName; 
  private String location; 
  private Date updateTime; 
 
  // Getters and Setters 
} 

  然后我们定义相应的 Customer.hbm.XML

清单 2. Customer.hbm.xml 配置文件的样例代码

  <hibernate-mapping package="com.sample.shards.bean"> 
    <class name="Customer" table="CUSTOMER"> 
     <id name="customerId" column="CUSTOMER_ID" type="big_integer"> 
       <generator class="org.hibernate.shards.id.ShardedUUIDGenerator"/> 
     </id> 
     <property name="tenantId" column="TENANT_ID"/> 
     <property name="firstName" column="FIRST_NAME"/> 
     <property name="lastName" column="LAST_NAME"/> 
     <property name="location" column="LOCATION"/> 
     <property name="updateTime" type="timestamp" column="UPDATE_TIME"/> 
  </class> 
</hibernate-mapping 

  接下来,我们配置 hibernate configuration xmls。起初,在一个共享数据库,共享模式方法中,只配置一个 hibernate.cfg.xml。随着数据库实例的增多,为碎片中使用的每个数据库实例添加 hibernate.cfg.xml。这些 Hibernate 配置文件在连接、会话工厂和共享 ID 细节方面有所不同。以下表格展示 hibernate0.cfg.xml — 示例中使用的三个配置文件之一。

清单 3. Hibernate0.cfg.xml 配置文件的样例代码

<hibernate-configuration> 
 <session-factory name="HibernateSessionFactory0"> 
  <property name="dialect">org.hibernate.dialect.HSQLDialect</property> 
  <property name="connection.driver_class">org.hsqldb.JDBCDriver</property> 
  <property name="connection.url">jdbc:hsqldb:mem:shard0</property> 
  <property name="connection.username">sa</property> 
  <property name="connection.password"></property> 
  <property name="hibernate.connection.shard_id">0</property> 
  <property name="hibernate.shard.enable_cross_shard_relationship_checks"> 
   true 
  </property> 
 </session-factory> 
</hibernate-configuration> 

  如上所示,Hibernate Shards 的配置类似于 Hibernate 的配置。主要区别在于添加了 hibernate.connection.shard_id 和 hibernate.shard.enable_cross_shard_relationship_checks 属性。还要注意,会话工厂和共享 ID 必须惟一地表示碎片。hibernate1.cfg.xml 和 hibernate2.cfg.xml 将以同样的方式得到配置。请注意,可以使用任何数据库。在本例中,我们为一个数据库实例使用了一个 HSQL Database Engine Server,为另外两个数据库实例使用了 HSQL Database Engine In-Memory。

  构建分片策略

  现在我们定义应用程序中要使用的分片策略。我们创建一个称为 CustomerShardSelectionStrategy 的自定义 ShardSelectionStrategy,它实现 ShardSelectionStrategy,基于承租者 ID 决定存储行的碎片。碎片 ID 将相对于后续步骤中配置的虚拟碎片 ID 映射予以映射。如果未提供虚拟碎片 ID 映射(可选),默认为物理碎片 ID 映射。

清单 4. CustomerShardSelectionStrategy 样例代码

public class CustomerShardSelectionStrategy implements ShardSelectionStrategy { 
 public ShardId selectShardIdForNewObject(Object obj) { 
   if (obj instanceof Customer) { 
   return getShardId(((Customer) obj).getTenantId()); 
   } 
   throw 
  new IllegalArgumentException(); 
  } 
 
  public ShardId getShardId(String tenantId) { 
   int shardId = 0; 
   if(tenantId.equalsIgnoreCase("Tenant1") || tenantId.equalsIgnoreCase("Tenant2")) 
     shardId = 0; 
   else 
  if(tenantId.equalsIgnoreCase("Tenant3")) 
     shardId = 1; 
   else 
  if(tenantId.equalsIgnoreCase("Tenant4")) 
     shardId = 2; 
   else 
  if(tenantId.equalsIgnoreCase("Tenant5")) 
     shardId = 3; 
   return 
  new ShardId(shardId); 
  } 
} 

  我们为其他策略使用 Hibernate Shards 提供的 AllShardsShardResolutionStrategy 和 SequentialShardAccessStrategy 实现。

  接下来,我们基于碎片策略实现构建碎片策略工厂。

清单 5. 构建碎片策略工厂

ShardStrategyFactory buildShardStrategyFactory() { 
  return new ShardStrategyFactory() { 
   public ShardStrategy newShardStrategy(List<ShardId> shardIds) { 
     ShardSelectionStrategy pss = new CustomerShardSelectionStrategy(); 
     ShardResolutionStrategy prs = new AllShardsShardResolutionStrategy(shardIds); 
     ShardAccessStrategy pas = new SequentialShardAccessStrategy(); 
     return new ShardStrategyImpl(pss, prs, pas); 
   } 
  }; 
} 

  然后我们通过传递 hibernate*.cfg.xml 和 customer.hbm.xml 配置文件获取 ShardSessionFactory。我们还创建 4 个用于未来需求的虚拟碎片,目前被映射到 3 个物理碎片上。如上一节所讨论的,我们创建一个虚拟碎片映射来处理数据库缩放的 4 个需求。如下所示,我们配置虚拟碎片 2 和 3 以将其映射到同一物理碎片 2。稍后添加新物理碎片时,仅需提供该映射以及 hibernate 配置文件。不需要对其他代码做任何更改。

清单 6. 用于创建会话工厂的样例代码

public SessionFactory createSessionFactory() { 
  Configuration config = new Configuration(); 
  config.configure("hibernate0.cfg.xml"); 
  config.addResource("customer.hbm.xml"); 
  
  List shardConfigs = new ArrayList(); 
  shardConfigs.add(buildShardConfig("hibernate0.cfg.xml")); 
  shardConfigs.add(buildShardConfig("hibernate1.cfg.xml")); 
  shardConfigs.add(buildShardConfig("hibernate2.cfg.xml")); 
  
  Map<Integer, Integer> virtualShardMap = new HashMap<Integer, Integer>(); 
  virtualShardMap.put(0, 0); 
  virtualShardMap.put(1, 1); 
  virtualShardMap.put(2, 2); 
  virtualShardMap.put(3, 2); 
 
  ShardStrategyFactory shardStrategyFactory = buildShardStrategyFactory(); 
  ShardedConfiguration shardedConfig = new ShardedConfiguration( 
                            config, 
                            shardConfigs, 
                            shardStrategyFactory, 
                            virtualShardMap); 
  return shardedConfig.buildShardedSessionFactory(); 
} 

  使用 Hibernate 执行数据库操作

  最后,我们使用获取的 SessionFactory 实例执行常规的 Hibernate 操作。

清单 7. 用于执行数据库操作的样例代码

Session session = sessionFactory.openSession(); 
try { 
  session.beginTransaction(); 
 
  // Add new records to the database 
  Customer customer = new Customer(); 
  customer.setFirstName("John"); 
  customer.setLastName("Doe"); 
  customer.setLocation("New York City"); 
  customer.setTenantId("Tenant1"); 
  customer.setUpdateTime(new Date()); 
  session.save(customer); 
 
  // Similarly add records for other Tenants... 
  session.getTransaction().commit(); 
 
  // List the customers from the database 
  List customers = getData(); 
  for (int i = 0; i < customers.size(); i++) { 
   Customer customer = (Customer) customers.get(i); 
   System.out.println(customer.toString()); 
  } 
} finally { 
  session.close(); 
} 

  代码输出

  在运行应用程序之后,控制台上显示以下结果。

清单 8. 控制台上显示的样例结果

Customer Id: 8006718453799964144490040897437701, Tenant Id: Tenant3, First Name: James, 
Last Name: Bond, Location: London, Update Time: 2010-03-16 09:28:33.526 
Customer Id: 13199015312334791773020537226657798, Tenant Id: Tenant4, First Name: Vijay, 
Last Name: Raj, Location: Bangalore, Update Time: 2010-03-16 09:28:33.526 
Customer Id: 18391312170869619401551033555877895, Tenant Id: Tenant5, First Name: Brett, 
Last Name: Lee, Location: Melbourne, Update Time: 2010-03-16 09:28:33.526 
Customer Id: 2814421595265136515959544563040259, Tenant Id: Tenant1, First Name: John, 
Last Name: Doe, Location: New York City, Update Time: 2010-03-16 09:28:33.401 
Customer Id: 2814421595265136515959544568217604, Tenant Id: Tenant2, First Name: Jane, 
Last Name: Dane, Location: San Francisco, Update Time: 2010-03-16 09:28:33.526 

  不同数据库中的 Customer 表会含有以下数据。

表 2. 数据库 1 中的 Customer 表

Customer_ID Tenant_ID First_Name Last_Name Location Update_Time
2814421595265136515959544563040259 tenant1 John Doe New York City 2010-03-16 09:24:31.839
2814421595265136515959544568217604 tenant2 Jane Dane San Francisco 2010-03-16 09:24:31.964


表 3. 数据库 2 中的 Customer 表

Customer_ID Tenant_ID First_Name Last_Name Location Update_Time
8006718453799964144490040897437701 tenant3 James Bond London 2010-03-16 09:28:33.526


表 4. 数据库 3 中的 Customer 表

Customer_ID Tenant_ID First_Name Last_Name Location Update_Time
13199015312334791773020537226657798 tenant4 Vijay Raj Bangalore 2010-03-16 09:28:33.526
18391312170869619401551033555877895 tenant5 Brett Lee Melbourne 2010-03-16 09:28:33.526

  Hibernate Shards Framework 的当前局限

  Hibernate Shards Framework (V3.0.0.Beta2) 的一些主要局限如下:

  ShardedSessionImpl、ShardedCriteriaImpl 和 ShardedQueryImpl 提供的一些我们很少用的方法还没有完全实现。

  到目前为止,分片框架不能用于 HQL,它只能用于 SQL。

  Hibernate Shards 不在一个非托管环境下对分布式事务提供支持。如果应用程序需要分布式事务,我们需要插入支持分布式事务的事务管理实现。

  目前,Hibernate Shards Framework 不支持交叉共享对象图。

  在通过 JPA 配置 SessionFactory 时当前配置机制不可行。

  结束语

  本文概述了 SaaS 应用程序的数据库分片需求,以及数据库分片的概念和使用开源 Hibernate Shards Framework 实现分片的可行性。本文还谈及了 Hibernate Shards Framework 目前的局限。本文将帮助架构师和开发人员有效地利用数据层中的 Hibernate Shards Framework,同时构建和设计真正基于 J2EE 的多租户 SaaS 应用程序。

  本文示例源代码或素材下载

上一篇: 真的,为什么要用 Hibernate?
下一篇: Java 开发 2.0: 使用 Hibernate Shards 进行切分

关于我们 | 联系我们 | 加入我们 | 广告服务 | 投诉意见 | 网站导航
Copyright © 2000-2011 21tx.com, All Rights Reserved.
晨新科技 版权所有 Created by TXSite.net