當(dāng)關(guān)系數(shù)據(jù)庫試圖在一個單一表中存儲數(shù) TB 的數(shù)據(jù)時,總性能經(jīng)常會降低。顯然,對所有數(shù)據(jù)編索引不僅對于讀而且對于寫都很耗時。因?yàn)?NoSQL 數(shù)據(jù)商店尤其適合存儲大型數(shù)據(jù)(如 Google 的 Bigtable),顯然 NoSQL 是一種非關(guān)系數(shù)據(jù)庫方法。對于傾向于使用 ACID-ity 和實(shí)體結(jié)構(gòu)關(guān)系數(shù)據(jù)庫的開發(fā)人員及需要這種結(jié)構(gòu)的項(xiàng)目來說,切分是一個令人振奮的可選方法。
切分 是數(shù)據(jù)庫分區(qū)的一個分支,但是它不是本地?cái)?shù)據(jù)庫技術(shù) — 切分發(fā)生在應(yīng)用程序級別。在各種切分實(shí)現(xiàn)中,Hibernate Shards 是 Java™ 技術(shù)世界中最受歡迎的一個。這個靈活絕妙的項(xiàng)目可以讓您使用映射至邏輯數(shù)據(jù)庫的 POJO 對切分?jǐn)?shù)據(jù)集進(jìn)行幾乎無縫操作(我將在下文簡要介紹 “幾乎” 的原因)。使用 Hibernate Shards 時,您無須將您的 POJO 特別映射至切分 — 您可以像使用 Hibernate 方法對任何常見關(guān)系數(shù)據(jù)庫進(jìn)行映射時一樣對其進(jìn)行映射。Hibernate Shards 可以為您管理低級別的切分任務(wù)。
事實(shí)上,Hibernate Shards 的編碼工作比較簡單。其中關(guān)鍵的部分在于判斷 如何進(jìn)行切分以及對什么進(jìn)行切分。
切分簡介
數(shù)據(jù)庫切分 是一個固有的關(guān)系流程,可以通過一些邏輯數(shù)據(jù)塊將一個表的行分為不同的小組。例如,如果您正在根據(jù)時間戳對一個名為 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)在同一個物理機(jī)上存儲數(shù)據(jù),所以受到硬件基礎(chǔ)的影響。除此之外, 分區(qū)也不能鑒別硬件的可靠性或者說缺乏可靠性。因此,很多智慧的人們開始尋找進(jìn)行伸縮的新方法。
切分 實(shí)質(zhì)上是數(shù)據(jù)庫級別的分區(qū):它不是通過數(shù)據(jù)塊分割數(shù)據(jù)表的行,而是通過一些邏輯數(shù)據(jù)元素對數(shù)據(jù)庫本身進(jìn)行分割(通常跨不同的計(jì)算機(jī))。也就是說,切分不是將數(shù)據(jù)表 分割成小塊,而是將整個數(shù)據(jù)庫 分割成小塊。
切分的一個典型示例是基于根據(jù)區(qū)域?qū)σ粋€存儲世界范圍客戶數(shù)據(jù)的大型數(shù)據(jù)庫進(jìn)行分割:切分 A 用于存儲美國的客戶信息,切分 B 用戶存儲亞洲的客戶信息,切分 C 歐洲,等。這些切分分別處于不同的計(jì)算機(jī)上,且每個切分將存儲所有相關(guān)數(shù)據(jù),如客戶喜好或訂購歷史。
切分的好處(如分區(qū)一樣)在于它可以壓縮大型數(shù)據(jù):單獨(dú)的數(shù)據(jù)表在每個切分中相對較小,這樣就可以支持更快速的讀寫速度,從而提高性能。切分 還可以改善可靠性,因?yàn)榧幢阋粋€切分意外失效,其他切分仍然可以服務(wù)數(shù)據(jù)。而且因?yàn)榍蟹质窃趹?yīng)用程序?qū)用孢M(jìn)行的,您可以對不支持常規(guī)分區(qū)的數(shù)據(jù)庫進(jìn)行切分 處理。資金成本較低同樣也是一個潛在優(yōu)勢。
切分和策略
像很多其他技術(shù)一樣,進(jìn)行切分時也需要作出部分妥協(xié)。因?yàn)榍蟹植皇且豁?xiàng)本地?cái)?shù)據(jù)庫技術(shù) — 也就是說,必須在應(yīng)用程序中實(shí)現(xiàn) —在開始切分之前需要制定出您的切分策略。進(jìn)行切分時主鍵和跨切分查詢都扮演重要角色,主要通過定義您不可以做什么實(shí)現(xiàn)。
主鍵
切分利用多個數(shù)據(jù)庫,其中所有數(shù)據(jù)庫都獨(dú)立起作用,不干涉其他切分。因此,如果您依賴于數(shù)據(jù)庫序列(如自動主鍵生成),很有可能在一個數(shù)據(jù)庫集中將出現(xiàn)同 一個主鍵??梢钥绶植际綌?shù)據(jù)庫協(xié)調(diào)序列,但是這樣會增加系統(tǒng)的復(fù)雜程度。避免相同主鍵最安全的方法就是讓應(yīng)用程序(應(yīng)用程序?qū)⒐芾砬蟹窒到y(tǒng))生成主鍵。
跨切分查詢
大部分切分實(shí)現(xiàn)(包括 Hibernate Shards)不支持跨切分查詢,這就意味著,如果您想利用不同切分的兩個數(shù)據(jù)集,就必須處理額外的長度。(有趣的是,Amazon 的 SimpleDB 也禁止跨域查詢)例如,如果將美國客戶信息存儲在切分 1 中,還需要將所有相關(guān)數(shù)據(jù)存儲在此。如果您嘗試將那些數(shù)據(jù)存儲在切分 2 中,情況就會變得復(fù)雜,系統(tǒng)性能也可能受影響。這種情況還與之前提到的一點(diǎn)有關(guān) — 如果您因?yàn)槟撤N原因需要進(jìn)行跨切分連接,最好采用一種可以消除重復(fù)的方式管理鍵!
很明顯,在建立數(shù)據(jù)庫前必須全面考慮切分策略。一旦選擇了一個特定的方向之后,您差不多就被它綁定了 — 進(jìn)行切分后很難隨便移動數(shù)據(jù)了。
一個策略示例
因?yàn)榍蟹謱⒛壎ㄔ谝粋€線型數(shù)據(jù)模型中(也就是說,您無法輕松連接不同切分中的數(shù)據(jù)),您必須對如何在每個切分中對數(shù)據(jù)進(jìn)行邏輯組織有一個清 晰的概念。這可以通過聚焦域中的主要節(jié)點(diǎn)實(shí)現(xiàn)。如在一個電子商務(wù)系統(tǒng)中,主要節(jié)點(diǎn)可以是一個訂單或者一個客戶。因此,如果您選擇 “客戶” 作為切分策略的節(jié)點(diǎn),那么與客戶有關(guān)的所有數(shù)據(jù)將移動至各自的切分中,但是您仍然必須選擇將這些數(shù)據(jù)移動至哪個切分。
對于客戶來說,您可以根據(jù)所在地(歐洲、亞洲、非洲等)切分,或者您也可以根據(jù)其他元素進(jìn)行切分。這由您決定。但是,您的切分策略應(yīng)該包含將 數(shù)據(jù)均勻分布至所有切分的方法。切分的總體概念是將大型數(shù)據(jù)集分割為小型數(shù)據(jù)集;因此,如果一個特定的電子商務(wù)域包含一個大型的歐洲客戶集以及一個相對小 的美國客戶集,那么基于客戶所在地的切分可能沒有什么意義。
回到比賽 — 使用切分!
現(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)比賽。
請注意:在進(jìn)行上述決定時,我已經(jīng)接受了一個妥協(xié):如果一個參賽者參加了不止一項(xiàng)比賽,他們分屬不同的切分,那該怎么辦 呢?Hibernate Shards (像大多數(shù)切分實(shí)現(xiàn)一樣)不支持跨切分連接。我必須忍受這些輕微不便,允許參賽者被包含在多個切分中 — 也就是說,我將在參賽者參加的多個比賽切分中重建該參賽者。
為了簡便起見,我將創(chuàng)建兩個切分:一個用于 10 英里以下的比賽;另一個用于 10 英里以上的比賽。
實(shí)現(xiàn) Hibernate Shards
Hibernate Shards 幾乎可以與現(xiàn)有 Hibernate 項(xiàng)目無縫結(jié)合使用。唯一問題是 Hibernate Shards 需要一些特定信息和行為。比如,需要一個切分訪問策略、一個切分選擇策略和一個切分處理策略。這些是您必須實(shí)現(xiàn)的接口,雖然部分情況下,您可以使用默認(rèn)策 略。我們將在后面的部分逐個了解各個接口。
ShardAccessStrategy
執(zhí)行查詢時,Hibernate Shards 需要一個決定首個切分、第二個切分及后續(xù)切分的機(jī)制。Hibernate Shards 無需確定查詢什么(這是 Hibernate Core 和基礎(chǔ)數(shù)據(jù)庫需要做的),但是它確實(shí)意識到,在獲得答案之前可能需要對多個切分進(jìn)行查詢。因此,Hibernate Shards 提供了兩種極具創(chuàng)意的邏輯實(shí)現(xiàn)方法:一種方法是根據(jù)序列機(jī)制(一次一個)對切分進(jìn)行查詢,直到獲得答案為止;另一種方法是并行訪問策略,這種方法使用一個 線程模型一次對所有切分進(jìn)行查詢。
為了使問題簡單,我將使用序列策略,名稱為 SequentialShardAccessStrategy
。我們將稍后對其進(jìn)行配置。
ShardSelectionStrategy
當(dāng)創(chuàng)建一個新對象時(例如,當(dāng)通過 Hibernate 創(chuàng)建一個新 Race
或 Runner
時),Hibernate Shards 需要知道需將對應(yīng)的數(shù)據(jù)寫入至哪些切分。因此,您必須實(shí)現(xiàn)該接口并對切分邏輯進(jìn)行編碼。如果您想進(jìn)行默認(rèn)實(shí)現(xiàn),有一個名為 RoundRobinShardSelectionStrategy
的策略,它使用一個循環(huán)策略將數(shù)據(jù)輸入切分中。
對于賽跑應(yīng)用程序,我需要提供根據(jù)比賽距離進(jìn)行切分的行為。因此,我們需要實(shí)現(xiàn) ShardSelectionStrategy
接口并提供依據(jù) Race
對象的 distance
采用 selectShardIdForNewObject
方法進(jìn)行切分的簡易邏輯。(我將稍候在 Race
對象中展示。)
運(yùn)行時,當(dāng)在我的域?qū)ο笊险{(diào)用某一類似 save
的方法時,該接口的行為將被深層用于 Hibernate 的核心。
清單 1. 一個簡單的切分選擇策略
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 中所看到的,如果持久化對象是一場 Race
,那么其距離被確定,而且(因此)選擇了一個切分。在這種情況下,有兩個切分:0 和 1,其中切分 1 中包含 10 英里以上的比賽,切分 0 中包含所有其他比賽。
如果持久化一個 Runner
或其他對象,情況會稍微復(fù)雜一些。我已經(jīng)編碼了一個邏輯規(guī)則,其中有三個原則:
- 一名
Runner
在沒有對應(yīng)的 Race
時無法存在。 - 如果
Runner
被創(chuàng)建時參加了多場 Race
s,這名 Runner
將被持久化到尋找到的首場 Race
所屬的切分中。(順便說一句,該原則對未來有負(fù)面影響。) - 如果還保存了其他域?qū)ο螅F(xiàn)在將引發(fā)一個異常。
然后,您就可以擦掉額頭的熱汗了,因?yàn)榇蟛糠制D難的工作已經(jīng)搞定了。隨著比賽應(yīng)用程序的增長,我所使用的邏輯可能會顯得不夠靈活,但是它完全可以順利地完成這次演示!
ShardResolutionStrategy
當(dāng)通過鍵搜索一個對象時,Hibernate Shards 需要一種可以決定首個切分的方法。將需要使用 SharedResolutionStrategy
接口對其進(jìn)行指引。
如我之前提到的那樣,切分迫使您重視主鍵,因?yàn)槟鷮⑿枰H自管理這些主鍵。幸運(yùn)的是,Hibernate 在提供鍵或 UUID 生成方面表現(xiàn)良好。因此 Hibernate Shards 創(chuàng)造性地提供一個 ID 生成器,名為 ShardedUUIDGenerator
,它可以靈活地將切分 ID 信息嵌入到 UUID 中。
如果您最后使用 ShardedUUIDGenerator
進(jìn)行鍵生成(我在本文中也將采取這種方法),那么您也可以使用 Hibernate Shards 提供的創(chuàng)新 ShardResolutionStrategy
實(shí)現(xiàn),名為 AllShardsShardResolutionStrategy
,這可以決定依據(jù)一個特定對象的 ID 搜索什么切分。
配置好 Hibernate Shards 工作所需的三個接口后,我們就可以對切分示例應(yīng)用程序的第二步進(jìn)行實(shí)現(xiàn)了。現(xiàn)在應(yīng)該啟動 Hibernate 的 SessionFactory
了。
配置 Hibernate Shards
Hibernate 的其中一個核心接口對象是它的 SessionFactory
。Hibernate 的所有神奇都是在其配置 Hibernate 應(yīng)用程序過程中通過這個小對象實(shí)現(xiàn)的,例如,通過加載映射文件和配置。如果您使用了注釋或 Hibernate 珍貴的 .hbm 文件,那么您還需要一個 SessionFactory
來讓 Hibernate 知道哪些對象是可以持久化的,以及將它們持久化到 哪里。
因此,使用 Hibernate Shards 時,您必須使用一個增強(qiáng)的 SessionFactory
類型來配置多個數(shù)據(jù)庫。它可以被命名為 ShardedSessionFactory
,而且它當(dāng)然是 SessionFactory
類型的。當(dāng)創(chuàng)建一個 ShardedSessionFactory
時,您必須提供之前配置好的三個切分實(shí)現(xiàn)類型(ShardAccessStrategy
、ShardSelectionStrategy
和 ShardResolutionStrategy
)。您還需提供 POJO 所需的所有映射文件。(如果您使用一個基于備注的 Hibernate POJO 配置,情況可能會有所不同。)最后,一個 ShardedSessionFactory
示例需要每個切分都對應(yīng)多個 Hibernate 配置文件。
創(chuàng)建一個 Hibernate 配置
我已經(jīng)創(chuàng)建了一個 ShardedSessionFactoryBuilder
類型,它有一個主要方法 createSessionFactory
,可以創(chuàng)建一個配置合理的 SessionFactory
。之后,我將將所有的一切都與 Spring 連接在一起(現(xiàn)在誰不使用一個 IOC 容器?)?,F(xiàn)在,清單 2 顯示了 ShardedSessionFactoryBuilder
的主要作用:創(chuàng)建一個 Hibernate 配置
:
清單 2. 創(chuàng)建一個 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)建了一個簡單的 Configuration
。 該文件包含如下信息,如使用的是什么類型的數(shù)據(jù)庫、用戶名和密碼等,以及所有必須的資源文件,如 POJO 所用的 .hbm 文件。在進(jìn)行切分的情況下,您通常需要使用多個數(shù)據(jù)庫配置,但是 Hibernate Shards 支持您僅使用一個 hibernate.cfg.xml 文件,從而簡化了整個過程(但是,如您在 清單 4 中所看到的,您將需要對使用的每一個切分準(zhǔn)備一個 hibernate.cfg.xml 文件)。
下一步,在清單 3 中,我將所有的切分配置都收集到了一個 List
中:
清單 3. 切分配置列表
List<ShardConfiguration> shardConfigs = new ArrayList<ShardConfiguration>(); for (String hibconfig : this.hibernateConfigurations) { shardConfigs.add(buildShardConfig(hibconfig)); }
|
Spring 配置
在 清單 3 中,對 hibernateConfigurations
的引用指向了 String
s List
,其中每個 String 都包含了 Hibernate 配置文件的名字。該 List
通過 Spring 自動連接。清單 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
正在與一個 POJO 映射文件和兩個切分配置文件連接。清單 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>
|
請注意,清單 5 中的 POJO 映射的唯一獨(dú)特方面是 ID 的生成器類 — 這就是 ShardedUUIDGenerator
,它(如您想象的一樣)將切分 ID 信息嵌入到 UUID 中。這就是我的 POJO 映射中切分的唯一獨(dú)特方面。
切分配置文件
下一步,如清單 6 中所示,我已經(jīng)配置了一個切分 — 在本示例中,除切分 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
屬性對跨切分關(guān)系進(jìn)行了檢查。根據(jù) Hibernate Shards 文檔記錄,該屬性非常耗時,在生成環(huán)境中應(yīng)該關(guān)閉。
最后,ShardedSessionFactoryBuilder
通過創(chuàng)建 ShardStrategyFactory
,然后添加三個類型(包括 清單 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í)行了那個名為 createSessionFactory
的絕妙方法,在本示例中創(chuàng)建了一個 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)建一個可以合理配置 ShardedSessionFactory
的生成器類,其實(shí)就是實(shí)現(xiàn) Hibernate 無處不在的 SessionFactory
類型。ShardedSessionFactory
完成了切分中所有的神奇。它利用我在 清單 1 中所部署的切分選擇策略,并從我配置的兩個切分中讀寫數(shù)據(jù)。(清單 6 顯示了切分 0 和切分 1 的配置幾乎相同。)
現(xiàn)在我需要做的就是連接我的域?qū)ο?,在本示例中,因?yàn)樗鼈円蕾囉?Hibernate,需要一個 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)建了一個類似工廠的 bean;也就是說,我的 RaceDAOImpl
類型有一個名為 sessionFactory
的屬性,是 SessionFactory
類型。之后,mySessionFactory
引用通過在 ShardedSessionFactoryBuilder
上調(diào)用 createSessionFactory
方法創(chuàng)建了一個 SessionFactory
示例,如 清單 4 中所示。
當(dāng)我為我的 Race
對象示例使用 Spring(我主要將其作為一個巨型工廠使用,以返回預(yù)配置的對象)時,一切事情就都搞定了。雖然沒有展示,RaceDAOImpl
類型是一個利用 Hibernate 模板進(jìn)行數(shù)據(jù)存儲和檢索的對象。我的 Race
類型包含一個 RaceDAOImpl
示例,它將所有與數(shù)據(jù)商店相關(guān)的活動都推遲至此。很默契,不是嗎?
請注意,我的 DAO 與 Hibernate Shards 在代碼方面并沒有綁定,而是通過配置進(jìn)行了綁定。配置(如 清單 5 中的)將它們綁定在一個特定切分 UUID 生成方案中,也就是說了我可以在需要切分時從已有 Hibernate 實(shí)現(xiàn)中重新使用域?qū)ο蟆?/p>
切分:使用 easyb 的測試驅(qū)動
接下來,我需要驗(yàn)證我的切分實(shí)現(xiàn)可以工作。我有兩個數(shù)據(jù)庫并通過距離進(jìn)行切分,所以當(dāng)我創(chuàng)建一場馬拉松時(10 英里以上的比賽),該 Race
示例應(yīng)在切分 1 中找到。一個小型的比賽,如 5 公里的比賽(3.1 英里),將在切分 0 中找到。創(chuàng)建一場 Race
后,我可以檢查單個數(shù)據(jù)庫的記錄。
在清單 10 中,我已經(jīng)創(chuàng)建了一場馬拉松,然后繼續(xù)驗(yàn)證記錄確實(shí)是在切分 1 中而非切分 0 中。使事情更加有趣(和簡單)的是,我使用了 easyb,這是一個基于 Groovy 的行為驅(qū)動開發(fā)架構(gòu),利用自然語言驗(yàn)證。easyb 也可以輕松處理 Java 代碼。即便不了解 Groovy 或 easyb,您也可以通過查看清單 10 中的代碼,看到一切如期進(jìn)行。(請注意,我?guī)椭鷦?chuàng)建了 easyb,并且在 developerWorks 中對這個話題發(fā)表過文章。)
清單 10. 一個驗(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)然,我的工作還沒有完 — 我還需要創(chuàng)建一個短程比賽,并驗(yàn)證其位于切分 0 中而非切分 1 中。您可以在本文提供的 代碼下載 中看到該驗(yàn)證操作!
切分的利弊
切分可以增加應(yīng)用程序的讀寫速度,尤其是如果您的應(yīng)用程序包含大量數(shù)據(jù) — 如數(shù) TB — 或者您的域處于無限制發(fā)展中,如 Google 或 Facebook。
在進(jìn)行切分之前,一定要確定應(yīng)用程序的規(guī)模和增長對其有利。切分的成本(或者說缺點(diǎn))包括對如何存儲和檢索數(shù)據(jù)的特定應(yīng)用程序邏輯進(jìn)行編碼的成本。進(jìn)行切分后,您多多少少都被鎖定在您的切分模型中,因?yàn)橹匦虑蟹植⒎且资隆?/p>
如果能夠正確實(shí)現(xiàn),切分可以用于解決傳統(tǒng) RDBMS 規(guī)模和速度問題。切分對于綁定于關(guān)系基礎(chǔ)架構(gòu)、無法繼續(xù)升級硬件以滿足大量可伸縮數(shù)據(jù)存儲要求的組織來說是一個非常成本高效的決策。