當(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è)新 Race
或 Runner
時(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í)參加了多場 Race
s,這名 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)類型(ShardAccessStrategy
、ShardSelectionStrategy
和 ShardResolutionStrategy
)。您還需提供 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
的引用指向了 String
s 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è)非常成本高效的決策。