數據庫分片(shard)是一種在數據庫的某些表變得特別大的時候采用的一種技術。
通過按照一定的維度將表切分,可以使該表在常用的檢索中保持較高的效率,而那些不常用的記錄則保存在低訪問表中。比如:銷售記錄按照時間來切分。(橫向切分)
也可以根據地域進行拆分,使得每個地區訪問自己的表從而進行負載均衡。(縱向切分)
也可以縱橫切分,使表拆的更細致。
也可以分庫,讓不同的數據存放在不同的服務器上,從而進一步均衡負載。
當遇到這樣的事情的時候,如果不是采用了MongoDB這種自動拆表的工具,一般來說,都要自己實現一下切表的策略。其實,Hibernate中已經提供了一個很好用的包:
Hiberante Shard,該包是Google貢獻給Hibernate社區的。根據其資料顯示,現在這個包還是有些限制的。
比如:不能夠進行跨表的order by,不能實現跨表的distinct,不能采用基礎數據類型(如int)作為ID的類型。
但是它可以支持跨表的唯一ID,跨表的查詢,跨表的累計...
而且它似乎只要少量的代碼和簡單的配置就可以使用,看來它真的是一個很好的工具。值得一試。
很不幸,網上的例子太少了,只找到了一段例子代碼:
這段代碼下載之后運行了,由於數據量太少,並且生成在同一個表中,無法證明Hibernate Shards的作用。
官方網站的資料似乎也是惜墨如金,沒有解釋的非常詳細。
Shards如何配置,如何使用呢?
一個工程里,有的表要切分,有的不必,如何做?
Shard和ebean如何結合使用呢?
帶着這些課題,我開始了對Hibernate Shard的調查和研究。
Hibernate是一種ORM的包,它要有來自mapping.xml的“原型”,來自Java的Entity才能夠形成ORMapping,還有一個數據庫的表,它們的關系是一對一。
而如果采用分片技術,那么應該是一個原型,一個Entity,對應數據庫的若干個結構相同的表。
在Hibernate Shard中,通過一個叫做“策略”的東西來完成這樣的過程。
它允許通過定義不同的策略,來將不同分類的數據存放在不同的表(乃至庫)中,而這個要通過一組和hibernate.cfg.xml結構一樣的配置文件來定義。就像下面這樣:
1 <?xml version='1.0' encoding='utf-8'?> 2 <!DOCTYPE hibernate-configuration PUBLIC 3 "-//Hibernate/Hibernate Configuration DTD//EN" 4 "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> 5 <hibernate-configuration> 6 <session-factory> 7 <property name="connection.driver_class">com.mysql.jdbc.Driver</property> 8 <property name="connection.url">jdbc:mysql://localhost/test</property> 9 <property name="connection.username">root</property> 10 <property name="connection.password">root</property> 11 <property name="connection.pool_size">10</property> 12 <property name="show_sql">true</property> 13 <property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property> 14 <property name="hbm2ddl.auto">validate</property> 15 <property name="hibernate.connection.shard_id">0</property> 16 <property name="hibernate.shard.enable_cross_shard_relationship_checks">false</property> 17 18 19 <mapping resource="ContactEntity.hbm.xml" /> 20 </session-factory> 21 22 </hibernate-configuration>
然后在生成SessionFactory的時候采用這樣的代碼:
1 private static SessionFactory createSessionFactory() { 2 Configuration prototypeCfg = new Configuration() 3 .configure("shard0.hibernate.cfg.xml"); 4 List<ShardConfiguration> shardCfgs = new ArrayList<ShardConfiguration>(); 5 shardCfgs.add(buildShardConfig("shard0.hibernate.cfg.xml")); 6 shardCfgs.add(buildShardConfig("shard1.hibernate.cfg.xml")); 7 ShardStrategyFactory strategyFactory = buildShardStrategyFactory(); 8 ShardedConfiguration shardedConfig = new ShardedConfiguration( 9 prototypeCfg, shardCfgs, strategyFactory); 10 return shardedConfig.buildShardedSessionFactory(); 11 }
而策略則分為三種:
ShardAccessStrategy
ShardSelectionStrategy
ShardResolutionStrategy
我們需要上述三種策略才能夠構建Hibernate的SessionFactory,就像下面這樣。
1 private static ShardStrategyFactory buildShardStrategyFactory() { 2 return new ShardStrategyFactory() { 3 public ShardStrategy newShardStrategy(List<ShardId> shardIds) { 4 ShardSelectionStrategy ss = new MyShardSelectionStrategy(shardIds); 5 ShardResolutionStrategy rs = new MyShardResolutionStrategy(shardIds); 6 ShardAccessStrategy as = new SequentialShardAccessStrategy(); 7 return new ShardStrategyImpl(ss, rs, as); 8 } 9 }; 10 }
那么這三種策略都是什么,應該怎么配置呢?
這三種策略的文檔說明和代碼說明不怎么一致。(說實在的,這段文檔真的沒怎么看懂,幸運的是,它是OpenSource的)
ShardAccessStrategy 文檔說,切片訪問策略,它定義了Hibernate如何和多個Shard之間進行訪問。
幸運的是,Hibernate已經為我們創建了兩個定義好了的ShardAccessStrategy,它們是:
SequentialShardAccessStrategy (順序切片訪問策略) 和 ParallelShardAccessStrategy(並行切片訪問策略)
順序切片訪問策略 如其名稱所言,它按照順序切片,資料顯示,它有可能在訪問無序數據時性能偏低,若是這種情況,官方建議使用LoadBalancedSequentialShardAccessStrategy。
並行切片訪問策略 如其名稱所言,它提供了並行訪問的策略,所以它同時要求提供一個並行策略執行器。 —— 聽起來挺難得,而且,介紹資料說——這超綱了。
先不管這么多吧,假設我們訪問的數據是一種,按地區、按年份增長的數據,每個城市個月增長量都在10萬~1百萬,那么我們要在這里采用什么策略呢?
數據是按照時間排序的,所以,我們可以采用SequentialShardAccessStrategy,按月分片,並且按照地區分片。
ShardSelectionStrategy 文檔說定義了如何創建一個新對象。
代碼上的注釋說:Determine the specific shard on which this object should reside
也就是說,這個是定義哪個領域用來存放這條數據的。
ShardResolutionStrategy 文檔說是表示如何將數據進行分流的。比如我們提到的按地區、按月分片。那么數據需要根據這些條件存放在不同的表中。而ShardResolutionStrategy就是幫助我們來完成這個動作的。
代碼注釋上說:Determine the shards on which an object might live
在ShardStrategyFactory的newShardStrategy方法中傳入的參數List<ShardId> shardIds會幫助我們進行選擇區域動作。ShardId會定位對應的Continent。
我跟蹤了一下那段例子代碼,這里的shardIds表示有多少個hibernate.cfg.xml文件中的不同的shard_id字段的值。而ShardSelectionStrategy在Insert的時候會執行,而ShardResolutionStrategy則會在Select的時候執行。(update/delete尚未嘗試。)
我們可以在ShardSelectionStrategy中建立自己的策略,比如,按照時間,按照地區來區分數據。從而把數據存放在不同的庫中。
因為shardx.hibernate.cfg.xml指定了不同的數據庫,所以,到這里可以實現分庫了。
而對於那些不必分表的直接return 0即可。
第一版的測試代碼在這里下載。
---------------
下一步我將研究一下,如何分表和如何結合Ebean。
