分庫分表 是解決mysql水平擴展的主要手段。
網上有關策略的討論很多,主要是hash擴展、按時間擴展、按范圍擴展等等。但真正想實施分庫分表的朋友們往往覺得“策略聽來終覺淺,覺知此事要代碼”,因此本文的主要目的是給朋友們提供一個可實現架構。
JDBCTemplate和Hibernate
大家都知道Hibernate是ORM(對象-關系數據庫 mapping)意義上的第一個真正的“統治級”產品。 JDBCTemplate則是對Spring對jdbc的簡單封裝,相對於Hibernate,工程師需要自己寫sql,而不是像Hibernate那樣直接操作對象解決數據庫持久化的問題。
因為暴露了sql,JDBCTemplate當然也不利於跨數據庫(畢竟每個數據庫的實現產品的sql也不竟相同)。但現在大多數互聯網企業都傾向於使用JDBCTemplate,而不是Hibernate。
個人認為主要原因就是性能問題:
(1) 為獲取更好性能,往往根據不同數據庫采用特有的優化方式,即使是DAO層全部用Hibernate實現,遷移數據庫也不是輕松的工作。
(2) 使用Hibernate處理關聯關系往往將大量數據信息加載到業務系統內存,而不是在數據庫系統中處理,只是將最終結果返回。這樣破壞了生產系統和DB的解耦,導致DB優化困難,以及生產系統的不安全。
(3) 分庫分表對於Hibernate來說顯得比較復雜
可以說第三個原因是主要的。本文會圍繞JDBCTemplate來實現分庫分表,如果你還在使用Hibernate,建議逐漸切換到JDCBTemplate。
分庫分表策略
分庫分表策略,簡單來說就是根據要被持久化的數據,分配一個庫或者表來讀/寫。因此DBSplitStrategy接口定義如下:
interface DBSplitStrategy {
String getDBName(long id); // 獲取庫名
String getTableSuffix(long id); // 獲取表名
JdbcTemplate getIdxJdbcTemplate(long id); // 獲取db jt
JdbcTemplate getIdxJdbcTemplate(String dbname); // 根據庫名獲取 db jt
JdbcTemplate getIdxJdbcTemplateByTable(String table); // 根據表名獲取db jt
}
接口定義是圍繞最基本的:key -> 邏輯庫名/表名 -> 物理庫名/表名
實現類
以最常見的HashSplit為例,首先我們需要幾個基本的配置項:
(1)基本庫名,也可以叫庫名前綴;
(2)分庫總數;
(3)分表總數;
(4)分庫對應的物理地址,即JDBCTemplate定義
Spring 配置
<bean id="dataService" class="DBSplitStrategy">
<property name="DBNameBase" value="session_" />
<property name="splitDBCount" value="16" />
<property name="splitTbCount" value="64" />
<property name="dmJts">
<map>
<entry key="session_1" value-ref="jts1"></entry>
<entry key="session_2" value-ref="jts2"></entry>
...
有了以上配置,代碼工作只需要把輸入的關鍵詞安裝策略轉換成邏輯庫名、表名即可,偽代碼如下:碼
public String getTableName(long id) {
long hash = getHash4split(id, splitCount);
return tbNameBase + String.valueOf(hash / shareDBCount + 1);
}
public String getDBName(long id) {
long hash = getHash4split(id, splitCount);
return dbNameBase + ( hash % shareDBCount + 1);
}
這段代碼里有個有趣的邏輯,如果你的業務主鍵從 1 一直增長,那么分庫分表的結果就是:庫1,表0;庫2,表0;庫3,表0;..... 庫1,表2;庫2,表2;...
總結
Mysql分庫分表,水平擴展還有很多問題這里沒有涉及到,比如,
如果最初分配的64個分表不夠用了怎么辦?這是最初決定分庫分表是需要考慮的重要問題,因為hash容易,rehash難。
這么多數據分散在不同的庫表中,怎么分析和挖掘呢?
怎么樣的分庫策略更適合你呢?