當(dāng)關(guān)系數(shù)據(jù)庫試圖在一個(gè)單一表中存儲(chǔ)數(shù) TB 的數(shù)據(jù)時(shí),總性能經(jīng)常會(huì)降低。顯然,對(duì)所有數(shù)據(jù)編索引不僅對(duì)于讀而且對(duì)于寫都很耗時(shí)。因?yàn)?NoSQL 數(shù)據(jù)商店尤其適合存儲(chǔ)大型數(shù)據(jù)(如 Google 的 Bigtable),顯然 NoSQL 是一種非關(guān)系數(shù)據(jù)庫方法。對(duì)于傾向于使用 ACID-ity 和實(shí)體結(jié)構(gòu)關(guān)系數(shù)據(jù)庫的開發(fā)人員及需要這種結(jié)構(gòu)的項(xiàng)目來說,切分是一個(gè)令人振奮的可選方法。

切分 是數(shù)據(jù)庫分區(qū)的一個(gè)分支,但是它不是本地?cái)?shù)據(jù)庫技術(shù) — 切分發(fā)生在應(yīng)用程序級(jí)別。在各種切分實(shí)現(xiàn)中,Hibernate Shards 是 Java™ 技術(shù)世界中最受歡迎的一個(gè)。這個(gè)靈活絕妙的項(xiàng)目可以讓您使用映射至邏輯數(shù)據(jù)庫的 POJO 對(duì)切分?jǐn)?shù)據(jù)集進(jìn)行幾乎無縫操作(我將在下文簡要介紹 “幾乎” 的原因)。使用 Hibernate Shards 時(shí),您無須將您的 POJO 特別映射至切分 — 您可以像使用 Hibernate 方法對(duì)任何常見關(guān)系數(shù)據(jù)庫進(jìn)行映射時(shí)一樣對(duì)其進(jìn)行映射。Hibernate Shards 可以為您管理低級(jí)別的切分任務(wù)。

事實(shí)上,Hibernate Shards 的編碼工作比較簡單。其中關(guān)鍵的部分在于判斷 如何進(jìn)行切分以及對(duì)什么進(jìn)行切分

切分簡介

數(shù)據(jù)庫切分 是一個(gè)固有的關(guān)系流程,可以通過一些邏輯數(shù)據(jù)塊將一個(gè)表的行分為不同的小組。例如,如果您正在根據(jù)時(shí)間戳對(duì)一個(gè)名為 foo 的超大型表進(jìn)行分區(qū),2010 年 8 月之前的所有數(shù)據(jù)都將進(jìn)入分區(qū) A,而之后的數(shù)據(jù)則全部進(jìn)入分區(qū) B。分區(qū)可以加快讀寫速度,因?yàn)樗鼈兊哪繕?biāo)是單獨(dú)分區(qū)中的較小型數(shù)據(jù)集。

分區(qū)功能并不總是可用的(MySQL 直到 5.1 版本后才支持),而且其需要的商業(yè)系統(tǒng)的成本也讓人望而卻步。更重要的是,大部分分區(qū)實(shí)現(xiàn)在同一個(gè)物理機(jī)上存儲(chǔ)數(shù)據(jù),所以受到硬件基礎(chǔ)的影響。除此之外, 分區(qū)也不能鑒別硬件的可靠性或者說缺乏可靠性。因此,很多智慧的人們開始尋找進(jìn)行伸縮的新方法。

切分 實(shí)質(zhì)上是數(shù)據(jù)庫級(jí)別的分區(qū):它不是通過數(shù)據(jù)塊分割數(shù)據(jù)表的行,而是通過一些邏輯數(shù)據(jù)元素對(duì)數(shù)據(jù)庫本身進(jìn)行分割(通常跨不同的計(jì)算機(jī))。也就是說,切分不是將數(shù)據(jù)表 分割成小塊,而是將整個(gè)數(shù)據(jù)庫 分割成小塊。

切分的一個(gè)典型示例是基于根據(jù)區(qū)域?qū)σ粋€(gè)存儲(chǔ)世界范圍客戶數(shù)據(jù)的大型數(shù)據(jù)庫進(jìn)行分割:切分 A 用于存儲(chǔ)美國的客戶信息,切分 B 用戶存儲(chǔ)亞洲的客戶信息,切分 C 歐洲,等。這些切分分別處于不同的計(jì)算機(jī)上,且每個(gè)切分將存儲(chǔ)所有相關(guān)數(shù)據(jù),如客戶喜好或訂購歷史。

切分的好處(如分區(qū)一樣)在于它可以壓縮大型數(shù)據(jù):單獨(dú)的數(shù)據(jù)表在每個(gè)切分中相對(duì)較小,這樣就可以支持更快速的讀寫速度,從而提高性能。切分 還可以改善可靠性,因?yàn)榧幢阋粋€(gè)切分意外失效,其他切分仍然可以服務(wù)數(shù)據(jù)。而且因?yàn)榍蟹质窃趹?yīng)用程序?qū)用孢M(jìn)行的,您可以對(duì)不支持常規(guī)分區(qū)的數(shù)據(jù)庫進(jìn)行切分 處理。資金成本較低同樣也是一個(gè)潛在優(yōu)勢。

切分和策略

像很多其他技術(shù)一樣,進(jìn)行切分時(shí)也需要作出部分妥協(xié)。因?yàn)榍蟹植皇且豁?xiàng)本地?cái)?shù)據(jù)庫技術(shù) — 也就是說,必須在應(yīng)用程序中實(shí)現(xiàn) —在開始切分之前需要制定出您的切分策略。進(jìn)行切分時(shí)主鍵和跨切分查詢都扮演重要角色,主要通過定義您不可以做什么實(shí)現(xiàn)。

主鍵
切分利用多個(gè)數(shù)據(jù)庫,其中所有數(shù)據(jù)庫都獨(dú)立起作用,不干涉其他切分。因此,如果您依賴于數(shù)據(jù)庫序列(如自動(dòng)主鍵生成),很有可能在一個(gè)數(shù)據(jù)庫集中將出現(xiàn)同 一個(gè)主鍵。可以跨分布式數(shù)據(jù)庫協(xié)調(diào)序列,但是這樣會(huì)增加系統(tǒng)的復(fù)雜程度。避免相同主鍵最安全的方法就是讓應(yīng)用程序(應(yīng)用程序?qū)⒐芾砬蟹窒到y(tǒng))生成主鍵。

跨切分查詢
大部分切分實(shí)現(xiàn)(包括 Hibernate Shards)不支持跨切分查詢,這就意味著,如果您想利用不同切分的兩個(gè)數(shù)據(jù)集,就必須處理額外的長度。(有趣的是,Amazon 的 SimpleDB 也禁止跨域查詢)例如,如果將美國客戶信息存儲(chǔ)在切分 1 中,還需要將所有相關(guān)數(shù)據(jù)存儲(chǔ)在此。如果您嘗試將那些數(shù)據(jù)存儲(chǔ)在切分 2 中,情況就會(huì)變得復(fù)雜,系統(tǒng)性能也可能受影響。這種情況還與之前提到的一點(diǎn)有關(guān) — 如果您因?yàn)槟撤N原因需要進(jìn)行跨切分連接,最好采用一種可以消除重復(fù)的方式管理鍵!

很明顯,在建立數(shù)據(jù)庫前必須全面考慮切分策略。一旦選擇了一個(gè)特定的方向之后,您差不多就被它綁定了 — 進(jìn)行切分后很難隨便移動(dòng)數(shù)據(jù)了。

一個(gè)策略示例

因?yàn)榍蟹謱⒛壎ㄔ谝粋€(gè)線型數(shù)據(jù)模型中(也就是說,您無法輕松連接不同切分中的數(shù)據(jù)),您必須對(duì)如何在每個(gè)切分中對(duì)數(shù)據(jù)進(jìn)行邏輯組織有一個(gè)清 晰的概念。這可以通過聚焦域中的主要節(jié)點(diǎn)實(shí)現(xiàn)。如在一個(gè)電子商務(wù)系統(tǒng)中,主要節(jié)點(diǎn)可以是一個(gè)訂單或者一個(gè)客戶。因此,如果您選擇 “客戶” 作為切分策略的節(jié)點(diǎn),那么與客戶有關(guān)的所有數(shù)據(jù)將移動(dòng)至各自的切分中,但是您仍然必須選擇將這些數(shù)據(jù)移動(dòng)至哪個(gè)切分。

對(duì)于客戶來說,您可以根據(jù)所在地(歐洲、亞洲、非洲等)切分,或者您也可以根據(jù)其他元素進(jìn)行切分。這由您決定。但是,您的切分策略應(yīng)該包含將 數(shù)據(jù)均勻分布至所有切分的方法。切分的總體概念是將大型數(shù)據(jù)集分割為小型數(shù)據(jù)集;因此,如果一個(gè)特定的電子商務(wù)域包含一個(gè)大型的歐洲客戶集以及一個(gè)相對(duì)小 的美國客戶集,那么基于客戶所在地的切分可能沒有什么意義。

回到比賽 — 使用切分!

現(xiàn)在讓我們回到我經(jīng)常提到的賽跑應(yīng)用程序示例中,我可以根據(jù)比賽或參賽者進(jìn)行切分。在本示例中,我將根據(jù)比賽進(jìn)行切分,因?yàn)槲铱吹接蚴歉鶕?jù)參 加不同比賽的參賽者進(jìn)行組織的。因此,比賽是域的根。我也將根據(jù)比賽距離進(jìn)行切分,因?yàn)楸荣悜?yīng)用程序包含不同長度和不同參賽者的多項(xiàng)比賽。

請(qǐng)注意:在進(jìn)行上述決定時(shí),我已經(jīng)接受了一個(gè)妥協(xié):如果一個(gè)參賽者參加了不止一項(xiàng)比賽,他們分屬不同的切分,那該怎么辦 呢?Hibernate Shards (像大多數(shù)切分實(shí)現(xiàn)一樣)不支持跨切分連接。我必須忍受這些輕微不便,允許參賽者被包含在多個(gè)切分中 — 也就是說,我將在參賽者參加的多個(gè)比賽切分中重建該參賽者。

為了簡便起見,我將創(chuàng)建兩個(gè)切分:一個(gè)用于 10 英里以下的比賽;另一個(gè)用于 10 英里以上的比賽。

實(shí)現(xiàn) Hibernate Shards

Hibernate Shards 幾乎可以與現(xiàn)有 Hibernate 項(xiàng)目無縫結(jié)合使用。唯一問題是 Hibernate Shards 需要一些特定信息和行為。比如,需要一個(gè)切分訪問策略、一個(gè)切分選擇策略和一個(gè)切分處理策略。這些是您必須實(shí)現(xiàn)的接口,雖然部分情況下,您可以使用默認(rèn)策 略。我們將在后面的部分逐個(gè)了解各個(gè)接口。

ShardAccessStrategy

執(zhí)行查詢時(shí),Hibernate Shards 需要一個(gè)決定首個(gè)切分、第二個(gè)切分及后續(xù)切分的機(jī)制。Hibernate Shards 無需確定查詢什么(這是 Hibernate Core 和基礎(chǔ)數(shù)據(jù)庫需要做的),但是它確實(shí)意識(shí)到,在獲得答案之前可能需要對(duì)多個(gè)切分進(jìn)行查詢。因此,Hibernate Shards 提供了兩種極具創(chuàng)意的邏輯實(shí)現(xiàn)方法:一種方法是根據(jù)序列機(jī)制(一次一個(gè))對(duì)切分進(jìn)行查詢,直到獲得答案為止;另一種方法是并行訪問策略,這種方法使用一個(gè) 線程模型一次對(duì)所有切分進(jìn)行查詢。

為了使問題簡單,我將使用序列策略,名稱為 SequentialShardAccessStrategy。我們將稍后對(duì)其進(jìn)行配置。

ShardSelectionStrategy

當(dāng)創(chuàng)建一個(gè)新對(duì)象時(shí)(例如,當(dāng)通過 Hibernate 創(chuàng)建一個(gè)新 RaceRunner 時(shí)),Hibernate Shards 需要知道需將對(duì)應(yīng)的數(shù)據(jù)寫入至哪些切分。因此,您必須實(shí)現(xiàn)該接口并對(duì)切分邏輯進(jìn)行編碼。如果您想進(jìn)行默認(rèn)實(shí)現(xiàn),有一個(gè)名為 RoundRobinShardSelectionStrategy 的策略,它使用一個(gè)循環(huán)策略將數(shù)據(jù)輸入切分中。

對(duì)于賽跑應(yīng)用程序,我需要提供根據(jù)比賽距離進(jìn)行切分的行為。因此,我們需要實(shí)現(xiàn) ShardSelectionStrategy 接口并提供依據(jù) Race 對(duì)象的 distance 采用 selectShardIdForNewObject 方法進(jìn)行切分的簡易邏輯。(我將稍候在 Race 對(duì)象中展示。)

運(yùn)行時(shí),當(dāng)在我的域?qū)ο笊险{(diào)用某一類似 save 的方法時(shí),該接口的行為將被深層用于 Hibernate 的核心。


清單 1. 一個(gè)簡單的切分選擇策略

				
import org.hibernate.shards.ShardId;
import org.hibernate.shards.strategy.selection.ShardSelectionStrategy;

public class RacerShardSelectionStrategy implements ShardSelectionStrategy {

public ShardId selectShardIdForNewObject(Object obj) {
if (obj instanceof Race) {
Race rce = (Race) obj;
return this.determineShardId(rce.getDistance());
} else if (obj instanceof Runner) {
Runner runnr = (Runner) obj;
if (runnr.getRaces().isEmpty()) {
throw new IllegalArgumentException("runners must have at least one race");
} else {
double dist = 0.0;
for (Race rce : runnr.getRaces()) {
dist = rce.getDistance();
break;
}
return this.determineShardId(dist);
}
} else {
throw new IllegalArgumentException("a non-shardable object is being created");
}
}

private ShardId determineShardId(double distance){
if (distance > 10.0) {
return new ShardId(1);
} else {
return new ShardId(0);
}
}
}

 

如您在 清單 1 中所看到的,如果持久化對(duì)象是一場 Race,那么其距離被確定,而且(因此)選擇了一個(gè)切分。在這種情況下,有兩個(gè)切分:0 和 1,其中切分 1 中包含 10 英里以上的比賽,切分 0 中包含所有其他比賽。

如果持久化一個(gè) Runner 或其他對(duì)象,情況會(huì)稍微復(fù)雜一些。我已經(jīng)編碼了一個(gè)邏輯規(guī)則,其中有三個(gè)原則:

  • 一名 Runner 在沒有對(duì)應(yīng)的 Race 時(shí)無法存在。
  • 如果 Runner 被創(chuàng)建時(shí)參加了多場 Races,這名 Runner 將被持久化到尋找到的首場 Race 所屬的切分中。(順便說一句,該原則對(duì)未來有負(fù)面影響。)
  • 如果還保存了其他域?qū)ο螅F(xiàn)在將引發(fā)一個(gè)異常。

然后,您就可以擦掉額頭的熱汗了,因?yàn)榇蟛糠制D難的工作已經(jīng)搞定了。隨著比賽應(yīng)用程序的增長,我所使用的邏輯可能會(huì)顯得不夠靈活,但是它完全可以順利地完成這次演示!

ShardResolutionStrategy

當(dāng)通過鍵搜索一個(gè)對(duì)象時(shí),Hibernate Shards 需要一種可以決定首個(gè)切分的方法。將需要使用 SharedResolutionStrategy 接口對(duì)其進(jìn)行指引。

如我之前提到的那樣,切分迫使您重視主鍵,因?yàn)槟鷮⑿枰H自管理這些主鍵。幸運(yùn)的是,Hibernate 在提供鍵或 UUID 生成方面表現(xiàn)良好。因此 Hibernate Shards 創(chuàng)造性地提供一個(gè) ID 生成器,名為 ShardedUUIDGenerator,它可以靈活地將切分 ID 信息嵌入到 UUID 中。

如果您最后使用 ShardedUUIDGenerator 進(jìn)行鍵生成(我在本文中也將采取這種方法),那么您也可以使用 Hibernate Shards 提供的創(chuàng)新 ShardResolutionStrategy 實(shí)現(xiàn),名為 AllShardsShardResolutionStrategy,這可以決定依據(jù)一個(gè)特定對(duì)象的 ID 搜索什么切分。

配置好 Hibernate Shards 工作所需的三個(gè)接口后,我們就可以對(duì)切分示例應(yīng)用程序的第二步進(jìn)行實(shí)現(xiàn)了。現(xiàn)在應(yīng)該啟動(dòng) Hibernate 的 SessionFactory 了。

配置 Hibernate Shards

Hibernate 的其中一個(gè)核心接口對(duì)象是它的 SessionFactory。Hibernate 的所有神奇都是在其配置 Hibernate 應(yīng)用程序過程中通過這個(gè)小對(duì)象實(shí)現(xiàn)的,例如,通過加載映射文件和配置。如果您使用了注釋或 Hibernate 珍貴的 .hbm 文件,那么您還需要一個(gè) SessionFactory 來讓 Hibernate 知道哪些對(duì)象是可以持久化的,以及將它們持久化到 哪里

因此,使用 Hibernate Shards 時(shí),您必須使用一個(gè)增強(qiáng)的 SessionFactory 類型來配置多個(gè)數(shù)據(jù)庫。它可以被命名為 ShardedSessionFactory,而且它當(dāng)然是 SessionFactory 類型的。當(dāng)創(chuàng)建一個(gè) ShardedSessionFactory 時(shí),您必須提供之前配置好的三個(gè)切分實(shí)現(xiàn)類型(ShardAccessStrategyShardSelectionStrategyShardResolutionStrategy)。您還需提供 POJO 所需的所有映射文件。(如果您使用一個(gè)基于備注的 Hibernate POJO 配置,情況可能會(huì)有所不同。)最后,一個(gè) ShardedSessionFactory 示例需要每個(gè)切分都對(duì)應(yīng)多個(gè) Hibernate 配置文件。

創(chuàng)建一個(gè) Hibernate 配置

我已經(jīng)創(chuàng)建了一個(gè) ShardedSessionFactoryBuilder 類型,它有一個(gè)主要方法 createSessionFactory,可以創(chuàng)建一個(gè)配置合理的 SessionFactory。之后,我將將所有的一切都與 Spring 連接在一起(現(xiàn)在誰不使用一個(gè) IOC 容器?)。現(xiàn)在,清單 2 顯示了 ShardedSessionFactoryBuilder 的主要作用:創(chuàng)建一個(gè) Hibernate 配置


清單 2. 創(chuàng)建一個(gè) Hibernate 配置

				
private Configuration getPrototypeConfig(String hibernateFile, List<String>
resourceFiles) {
Configuration config = new Configuration().configure(hibernateFile);
for (String res : resourceFiles) {
configs.addResource(res);
}
return config;
}

 

如您在 清單 2 中所看到的,可以從 Hibernate 配置文件中創(chuàng)建了一個(gè)簡單的 Configuration。 該文件包含如下信息,如使用的是什么類型的數(shù)據(jù)庫、用戶名和密碼等,以及所有必須的資源文件,如 POJO 所用的 .hbm 文件。在進(jìn)行切分的情況下,您通常需要使用多個(gè)數(shù)據(jù)庫配置,但是 Hibernate Shards 支持您僅使用一個(gè) hibernate.cfg.xml 文件,從而簡化了整個(gè)過程(但是,如您在 清單 4 中所看到的,您將需要對(duì)使用的每一個(gè)切分準(zhǔn)備一個(gè) hibernate.cfg.xml 文件)。

下一步,在清單 3 中,我將所有的切分配置都收集到了一個(gè) List 中:


清單 3. 切分配置列表

				
List<ShardConfiguration> shardConfigs = new ArrayList<ShardConfiguration>();
for (String hibconfig : this.hibernateConfigurations) {
shardConfigs.add(buildShardConfig(hibconfig));
}

 

Spring 配置

清單 3 中,對(duì) hibernateConfigurations 的引用指向了 Strings List,其中每個(gè) String 都包含了 Hibernate 配置文件的名字。該 List 通過 Spring 自動(dòng)連接。清單 4 是我的 Spring 配置文件中的一段摘錄:


清單 4. Spring 配置文件中的一部分

				
<bean id="shardedSessionFactoryBuilder"
class="org.disco.racer.shardsupport.ShardedSessionFactoryBuilder">
<property name="resourceConfigurations">
<list>
<value>racer.hbm.xml</value>
</list>
</property>
<property name="hibernateConfigurations">
<list>
<value>shard0.hibernate.cfg.xml</value>
<value>shard1.hibernate.cfg.xml</value>
</list>
</property>
</bean>

 

如您在 清單 4 中所看到的,ShardedSessionFactoryBuilder 正在與一個(gè) POJO 映射文件和兩個(gè)切分配置文件連接。清單 5 中是 POJO 文件的一段摘錄:


清單 5. 比賽 POJO 映射

				
<class name="org.disco.racer.domain.Race" table="race"dynamic-update="true"
dynamic-insert="true">

<id name="id" column="RACE_ID" unsaved-value="-1">
<generator class="org.hibernate.shards.id.ShardedUUIDGenerator"/>
</id>

<set name="participants" cascade="save-update" inverse="false" table="race_participants"
lazy="false">
<key column="race_id"/>
<many-to-many column="runner_id" class="org.disco.racer.domain.Runner"/>
</set>

<set name="results" inverse="true" table="race_results" lazy="false">
<key column="race_id"/>
<one-to-many class="org.disco.racer.domain.Result"/>
</set>

<property name="name" column="NAME" type="string"/>
<property name="distance" column="DISTANCE" type="double"/>
<property name="date" column="DATE" type="date"/>
<property name="description" column="DESCRIPTION" type="string"/>
</class>

 

請(qǐng)注意,清單 5 中的 POJO 映射的唯一獨(dú)特方面是 ID 的生成器類 — 這就是 ShardedUUIDGenerator,它(如您想象的一樣)將切分 ID 信息嵌入到 UUID 中。這就是我的 POJO 映射中切分的唯一獨(dú)特方面。

切分配置文件

下一步,如清單 6 中所示,我已經(jīng)配置了一個(gè)切分 — 在本示例中,除切分 ID 和連接信息外,切分 0 和切分 1 的文件是一樣的。


清單 6. Hibernate Shards 配置文件

				
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<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:file:/.../db01/db01
</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>

 

如其名字所示,enable_cross_shard_relationship_checks 屬性對(duì)跨切分關(guān)系進(jìn)行了檢查。根據(jù) Hibernate Shards 文檔記錄,該屬性非常耗時(shí),在生成環(huán)境中應(yīng)該關(guān)閉。

最后,ShardedSessionFactoryBuilder 通過創(chuàng)建 ShardStrategyFactory ,然后添加三個(gè)類型(包括 清單 1 中的 RacerShardSelectionStrategy),將一切都整合到了一起,如清單 7 中所示:


清單 7. 創(chuàng)建 ShardStrategyFactory

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

 

最后,我執(zhí)行了那個(gè)名為 createSessionFactory 的絕妙方法,在本示例中創(chuàng)建了一個(gè) ShardedSessionFactory,如清單 8 所示:


清單 8. 創(chuàng)建 ShardedSessionFactory

				
public SessionFactory createSessionFactory() {
Configuration prototypeConfig = this.getPrototypeConfig
(this.hibernateConfigurations.get(0), this.resourceConfigurations);

List<ShardConfiguration> shardConfigs = new ArrayList<ShardConfiguration>();
for (String hibconfig : this.hibernateConfigurations) {
shardConfigs.add(buildShardConfig(hibconfig));
}

ShardStrategyFactory shardStrategyFactory = buildShardStrategyFactory();
ShardedConfiguration shardedConfig = new ShardedConfiguration(
prototypeConfig, shardConfigs,shardStrategyFactory);
return shardedConfig.buildShardedSessionFactory();
}

 

使用 Spring 連接域?qū)ο?/a>

現(xiàn)在可以深呼吸一下了,因?yàn)槲覀凂R上就成功了。到目前為止,我已經(jīng)創(chuàng)建一個(gè)可以合理配置 ShardedSessionFactory 的生成器類,其實(shí)就是實(shí)現(xiàn) Hibernate 無處不在的 SessionFactory 類型。ShardedSessionFactory 完成了切分中所有的神奇。它利用我在 清單 1 中所部署的切分選擇策略,并從我配置的兩個(gè)切分中讀寫數(shù)據(jù)。(清單 6 顯示了切分 0 和切分 1 的配置幾乎相同。)

現(xiàn)在我需要做的就是連接我的域?qū)ο螅诒臼纠校驗(yàn)樗鼈円蕾囉?Hibernate,需要一個(gè) SessionFactory 類型進(jìn)行工作。我將僅使用我的 ShardedSessionFactoryBuilder 提供一種 SessionFactory 類型,如清單 9 中所示:


清單 9. 在 Spring 中連接 POJO

				
<bean id="mySessionFactory"
factory-bean="shardedSessionFactoryBuilder"
factory-method="createSessionFactory">
</bean>

<bean id="race_dao" class="org.disco.racer.domain.RaceDAOImpl">
<property name="sessionFactory">
<ref bean="mySessionFactory"/>
</property>
</bean>

 

如您在 清單 9 中所看到的,我首先在 Spring 中創(chuàng)建了一個(gè)類似工廠的 bean;也就是說,我的 RaceDAOImpl 類型有一個(gè)名為 sessionFactory 的屬性,是 SessionFactory 類型。之后,mySessionFactory 引用通過在 ShardedSessionFactoryBuilder 上調(diào)用 createSessionFactory 方法創(chuàng)建了一個(gè) SessionFactory 示例,如 清單 4 中所示。

當(dāng)我為我的 Race 對(duì)象示例使用 Spring(我主要將其作為一個(gè)巨型工廠使用,以返回預(yù)配置的對(duì)象)時(shí),一切事情就都搞定了。雖然沒有展示,RaceDAOImpl 類型是一個(gè)利用 Hibernate 模板進(jìn)行數(shù)據(jù)存儲(chǔ)和檢索的對(duì)象。我的 Race 類型包含一個(gè) RaceDAOImpl 示例,它將所有與數(shù)據(jù)商店相關(guān)的活動(dòng)都推遲至此。很默契,不是嗎?

請(qǐng)注意,我的 DAO 與 Hibernate Shards 在代碼方面并沒有綁定,而是通過配置進(jìn)行了綁定。配置(如 清單 5 中的)將它們綁定在一個(gè)特定切分 UUID 生成方案中,也就是說了我可以在需要切分時(shí)從已有 Hibernate 實(shí)現(xiàn)中重新使用域?qū)ο蟆?/p>

切分:使用 easyb 的測試驅(qū)動(dòng)

接下來,我需要驗(yàn)證我的切分實(shí)現(xiàn)可以工作。我有兩個(gè)數(shù)據(jù)庫并通過距離進(jìn)行切分,所以當(dāng)我創(chuàng)建一場馬拉松時(shí)(10 英里以上的比賽),該 Race 示例應(yīng)在切分 1 中找到。一個(gè)小型的比賽,如 5 公里的比賽(3.1 英里),將在切分 0 中找到。創(chuàng)建一場 Race 后,我可以檢查單個(gè)數(shù)據(jù)庫的記錄。

在清單 10 中,我已經(jīng)創(chuàng)建了一場馬拉松,然后繼續(xù)驗(yàn)證記錄確實(shí)是在切分 1 中而非切分 0 中。使事情更加有趣(和簡單)的是,我使用了 easyb,這是一個(gè)基于 Groovy 的行為驅(qū)動(dòng)開發(fā)架構(gòu),利用自然語言驗(yàn)證。easyb 也可以輕松處理 Java 代碼。即便不了解 Groovy 或 easyb,您也可以通過查看清單 10 中的代碼,看到一切如期進(jìn)行。(請(qǐng)注意,我?guī)椭鷦?chuàng)建了 easyb,并且在 developerWorks 中對(duì)這個(gè)話題發(fā)表過文章。)


清單 10. 一個(gè)驗(yàn)證切分正確性的 easyb 故事中一段摘錄

				
scenario "races greater than 10.0 miles should be in shard 1 or db02", {
given "a newly created race that is over 10.0 miles", {
new Race("Leesburg Marathon", new Date(), 26.2,
"Race the beautiful streets of Leesburg!").create()
}
then "everything should work fine w/respect to Hibernate", {
rce = Race.findByName("Leesburg Marathon")
rce.distance.shouldBe 26.2
}
and "the race should be stored in shard 1 or db02", {
sql = Sql.newInstance(db02url, name, psswrd, driver)
sql.eachRow("select race_id, distance, name from race where name=?",
["Leesburg Marathon"]) { row ->
row.distance.shouldBe 26.2
}
sql.close()
}
and "the race should NOT be stored in shard 0 or db01", {
sql = Sql.newInstance(db01url, name, psswrd, driver)
sql.eachRow("select race_id, distance, name from race where name=?",
["Leesburg Marathon"]) { row ->
fail "shard 0 contains a marathon!"
}
sql.close()
}
}

 

當(dāng)然,我的工作還沒有完 — 我還需要?jiǎng)?chuàng)建一個(gè)短程比賽,并驗(yàn)證其位于切分 0 中而非切分 1 中。您可以在本文提供的 代碼下載 中看到該驗(yàn)證操作!

切分的利弊

切分可以增加應(yīng)用程序的讀寫速度,尤其是如果您的應(yīng)用程序包含大量數(shù)據(jù) — 如數(shù) TB — 或者您的域處于無限制發(fā)展中,如 Google 或 Facebook。

在進(jìn)行切分之前,一定要確定應(yīng)用程序的規(guī)模和增長對(duì)其有利。切分的成本(或者說缺點(diǎn))包括對(duì)如何存儲(chǔ)和檢索數(shù)據(jù)的特定應(yīng)用程序邏輯進(jìn)行編碼的成本。進(jìn)行切分后,您多多少少都被鎖定在您的切分模型中,因?yàn)橹匦虑蟹植⒎且资隆?/p>

如果能夠正確實(shí)現(xiàn),切分可以用于解決傳統(tǒng) RDBMS 規(guī)模和速度問題。切分對(duì)于綁定于關(guān)系基礎(chǔ)架構(gòu)、無法繼續(xù)升級(jí)硬件以滿足大量可伸縮數(shù)據(jù)存儲(chǔ)要求的組織來說是一個(gè)非常成本高效的決策。