在專案中使用分庫分表中介軟體Zdal
轉載:https://www.tuicool.com/articles/ENVFNjJ
在博主前面的一篇文章 《構建分庫分表中介軟體Zdal》 中已經基本介紹瞭如何構建Zdal,這篇文章主要介紹如何在傳統的Java Web專案中引入Zdal,來達到分庫或者分表的目的,本文是《zdal設計文件》的實戰補充。文章中用到的演示的Java Web專案地址為 https://github.com/yuanwhy/fantasy .
新增zdal-client依賴
在Zdal專案中有很多模組,這些模組分別有著不同的功能和角色,在zdal-doc中的《zdal設計文件》中是這麼給每個模組定義的:
- zdal-client
- zdal-datasource : 管理訪問資料庫連線的元件,控制app對資料庫資源的使用,目前支援mysql,oracle,db2等資料庫的資料訪問
- zdal-parser : 手工編寫的高效能的方便擴充套件的SQL Parser,支援MySQL、Oracle,DB2等流行關係資料庫的SQL Parser
- zdal-rule : 在分庫分表中,根據拆分欄位進行選庫選表的元件,基於groovy規則引擎
- zdal-common : Zdal功能所用到一些公共元件類
我們在使用zdal中介軟體的時候其實只需要在POM中引用zdal-client即可,zdal-client已經依賴了內部其他元件,同時建議剔除掉對Spring的引用,主要是為了防止Spring版本的衝突:
<dependency>
<groupId>com.alipay.zdal</groupId>
<artifactId>zdal-client</artifactId>
<version>0.0.1</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId> spring</artifactId>
</exclusion>
</exclusions>
</dependency>
DataSource替換成ZdalDataSource
筆者在《分庫分表技術概覽》一文中總結過分庫分表現在主要是兩種解決方案:應用層依賴類中介軟體和中間層代理類中介軟體。Zdal屬於應用層依賴類中介軟體,主要是通過重寫JDBC介面的方式來實現的,應用層使用ORM框架並沒有本質的影響,因為ORM框架底層還是用的JDBC技術來訪問資料庫。
在JDBC規範中主要是通過DataSource來獲取資料庫連線,而Zdal也提供了這樣一種與以往不同的DataSource。傳統專案中可能使用c3p0這種有連線池功能的DataSource,類似於這種配置:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/fantasy"/>
<property name="user" value="root"/>
<property name="password" value=""/>
</bean>
現在Zdal中介軟體為了對上層儘可能的透明,儘量少的修改業務程式碼,把分庫分表的邏輯封裝在DataSource下面,對上層提供ZdalDataSource的實現,使用者替換掉老的c3p0資料來源即可:
<bean id="dataSource" class="com.alipay.zdal.client.jdbc.ZdalDataSource" init-method="init" destroy-method="close">
<property name="appName" value="fantasy"/>
<property name="appDsName" value="fantasyDs"/>
<property name="dbmode" value="dev" />
<property name="configPath" value="classpath:zdal" />
</bean>
這裡的appName即應用專案的名字,appDsName是真實資料來源的名字,dbmode的初衷是為了區分開發和生產環境(其實現在很多maven專案可以通過profile和命令列指定的方式來指定特定環境的檔案,這種dbmode的方式其實作用不大),configPath是真實資料庫配置和規則配置檔案的基本地址。
原來的zdal程式碼中configPath不支援從classpath中載入,可以在ZdalConfigurationLoader中刪除 File configurationFile
= new File(configPath, MessageFormat.format(Constants.LOCAL_CONFIG_FILENAME_SUFFIX, appName, dbMode))
這種檢驗檔案是否存在的程式碼,因為用 java.io.File
類就要求配置檔案必須是在檔案系統中真實存在,而實際上配置檔案可能在jar包中,並沒有對應於檔案系統中的檔案,而且Spring載入檔案的時候自己會檢驗檔案是否存在,修改後的getZdalConfigurationFromLocal方法如下:
private synchronized Map<String, ZdalConfig> getZdalConfigurationFromLocal(String appName,
String dbMode,
String appDsName,
String configPath) {
List<String> zdalConfigurationFilePathList = new ArrayList<String>();
zdalConfigurationFilePathList.add(configPath + "/" + MessageFormat.format(
Constants.LOCAL_CONFIG_FILENAME_SUFFIX, appName, dbMode));
zdalConfigurationFilePathList.add(configPath + "/" + MessageFormat.format(
Constants.LOCAL_RULE_CONFIG_FILENAME_SUFFIX, appName, dbMode));
return loadZdalConfigurationContext(zdalConfigurationFilePathList
.toArray(new String[zdalConfigurationFilePathList.size()]), appName, dbMode);
}
這樣修改後loadZdalConfigurationContext中使用FileSystemXmlApplicationContext就可以支援classpath、file字首來載入不同協議的XML檔案。
在配置檔案中宣告分庫分表規則
zdal的配置檔案主要有兩個,並且預設使用了一種規則,從getZdalConfigurationFromLocal的程式碼中可以看出來,即 appName-dbMode-ds.xml和appName-dbMode-rule.xml ,這裡的appName和dbMode要和dataSource的設定以及兩個配置檔案內部bean中的value保持一致。
比如筆者在Github中的fantasy專案的配置檔案為fantasy-dev-ds.xml,主要用來指定物理資料來源有哪些,是使用group、shard、shardfailover、shardgroup的哪種模式,內容如下:
<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fantasy" class="com.alipay.zdal.client.config.bean.ZdalAppBean">
<property name="appName" value="fantasy" />
<property name="dbmode" value="dev" />
<property name="appDataSourceList">
<list>
<ref bean="fantasyDs" />
</list>
</property>
</bean>
<bean id="fantasyDs" class="com.alipay.zdal.client.config.bean.AppDataSourceBean">
<property name="appDataSourceName" value="fantasyDs" />
<property name="dataBaseType" value="MYSQL" />
<property name="configType" value="SHARD" />
<property name="appRule" ref="fantasyRule"/>
<property name="physicalDataSourceSet">
<set>
<ref bean="physics0"/>
</set>
</property>
</bean>
<bean id="physics0" class="com.alipay.zdal.client.config.bean.PhysicalDataSourceBean" >
<property name="name" value="master_0" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/fantasy" />
<property name="userName" value="root" />
<property name="password" value="" />
<property name="minConn" value="1" />
<property name="maxConn" value="10" />
<property name="blockingTimeoutMillis" value="180" />
<property name="idleTimeoutMinutes" value="180" />
<property name="preparedStatementCacheSize" value="100" />
<property name="queryTimeout" value="180" />
<property name="prefill" value="true"/>
<property name="connectionProperties">
<map>
<entry key="connectTimeout" value="500" />
<entry key="autoReconnect" value="true" />
<entry key="initialTimeout" value="1" />
<entry key="maxReconnects" value="2" />
<entry key="socketTimeout" value="5000" />
<entry key="failOverReadOnly" value="false" />
</map>
</property>
</bean>
</beans>
另外一個重要的檔案是fantasy-dev-rule.xml,主要用來描述分庫或者分表字段是哪些,是SQL語句執行的依據:
<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="fantasyRule" class="com.alipay.zdal.rule.config.beans.AppRule">
<property name="masterRule" ref="fantasyRWRule" />
<property name="slaveRule" ref="fantasyRWRule" />
</bean>
<bean id="fantasyRWRule" class="com.alipay.zdal.rule.config.beans.ShardRule">
<property name="tableRules">
<map>
<entry key="user" value-ref="userTableRule" />
</map>
</property>
</bean>
<bean id="userTableRule" class="com.alipay.zdal.rule.config.beans.TableRule"
init-method="init">
<property name="tbSuffix" value="resetForEachDB:[_0-_1]"/>
<property name="dbIndexes" value="master_0"/>
<property name="tbRuleArray">
<list>
<value>
return com.yuanwhy.fantasy.rule.ShardRuleParser.parserTbIndex(#id#);
</value>
</list>
</property>
</bean>
</beans>
這裡的示例就是對user表分表,以使用者的id為分表字段,解析的方法在 com.yuanwhy.fantasy.rule.ShardRuleParser.parserTbIndex
中,tbSuffix是resetForEachDB方式,即每個分庫都有user_0、user_1表,只不過這裡恰好只有一個分庫;分表規則設計為id
% 10的方式獲取分表字尾,那麼對於id為10的使用者則被分配到user_0表(關於更多的分表規則的資訊可以參考《zdal設計文件》)。
將fantasy專案執行之前先執行user-schema.sql初始化語句,通過訪問 http://localhost:8080?id=10 執行起來後,可以從Debug資訊中觀察到原始的語句以及根據分表字段解析後獲得的真實可執行的語句:
[DEBUG]
[original sql]:SELECT id, name, age FROM user
WHERE id = ?
[master_0.user_0]:SELECT id, name, age FROM user_0
WHERE id = ?
至此,一個傳統的Java Web專案就具有了分庫分表的能力(這裡只演示了分表,分庫的道理是一樣的)。