1. 簡(jiǎn)介
Sharding-JDBC 是當(dāng)當(dāng)開(kāi)源的數(shù)據(jù)庫(kù)分庫(kù)分表中間件,同時(shí)也支持讀寫分離。
Sharding-JDBC 定位為輕量級(jí) java 框架,使用客戶端直連數(shù)據(jù)庫(kù),以 jar 包形式提供服務(wù),未使用中間層,無(wú)需額外部署,無(wú)其他依賴,DBA 也無(wú)需改變?cè)械倪\(yùn)維方式,可理解為增強(qiáng)版的 JDBC 驅(qū)動(dòng),舊代碼遷移成本幾乎為零。
Sharding-JDBC 架構(gòu)的核心邏輯為分片規(guī)則配置、SQL解析、SQL路由、SQL改寫、SQL執(zhí)行以及結(jié)果歸并模塊。

2. 客戶端配置
2.1 添加 maven 依賴
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>2.0.3</version>
</dependency>
2.2 分片算法
Sharding-JDBC 提供了以下 5 種分片策略,都繼承至ShardingStrategy:
StandardShardingStrategy
標(biāo)準(zhǔn)分片策略(最常用)。提供對(duì) SQL語(yǔ) 句中的 =, IN 和 BETWEEN AND的分片操作支持。
StandardShardingStrategy 只支持單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個(gè)分片算法。
-
PreciseShardingAlgorithm是必選的,用于處理 = 和 IN 的分片。 -
RangeShardingAlgorithm是可選的,用于處理 BETWEEN AND 分片,如果不配置RangeShardingAlgorithm,SQL中的 BETWEEN AND 將按照全庫(kù)路由處理。
ComplexShardingStrategy
復(fù)合分片策略。提供對(duì) SQL 語(yǔ)句中的 =, IN 和 BETWEEN AND 的分片操作支持。
ComplexShardingStrategy 支持多分片鍵,由于多分片鍵之間的關(guān)系復(fù)雜,因此 Sharding-JDBC 并未做過(guò)多的封裝,而是直接將分片鍵值組合以及分片操作符交于算法接口,完全由應(yīng)用開(kāi)發(fā)者實(shí)現(xiàn),提供最大的靈活度。
InlineShardingStrategy
Inline 表達(dá)式分片策略。使用 Groovy 的 Inline 表達(dá)式,提供對(duì) SQL 語(yǔ)句中的 = 和 IN 的分片操作支持。
InlineShardingStrategy 只支持單分片鍵,對(duì)于簡(jiǎn)單的分片算法,可以通過(guò)簡(jiǎn)單的配置使用,從而避免繁瑣的 Java 代碼開(kāi)發(fā),如: tuser${user_id % 8} 表示 t_user 表按照 user_id 對(duì) 8 取模分成 8 個(gè)表,表名稱為 t_user_0 到 t_user_7。
HintShardingStrategy
通過(guò) Hint 而非 SQL 解析的方式分片的策略。
NoneShardingStrategy
不分片的策略。
2.3 自定義分片算法
Sharding-JDBC 提供了以下4種算法接口:
- PreciseShardingAlgorithm
- RangeShardingAlgorithm
- HintShardingAlgorithm
- ComplexKeysShardingAlgorithm
現(xiàn)在我們有個(gè)分表需求,將時(shí)間字段,如repay_date(yyyy-MM-dd)按照季度分表,例如:“table_2019q1,table_2019q2,table_2019q3,table_2019q4,...”。由于時(shí)間的比較方式通常為區(qū)間比較,于是我們可以采用StandardShardingStrategy下的分片策略。
a、PreciseShardingAlgorithm 實(shí)現(xiàn):(Precise 處理 = 和 in 的路由)
public class DatePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Date> {
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) {
Assert.notNull(shardingValue.getValue(), "分表鍵不能為空");
String quarterMonth = DateShardingUtils.getYearQuarter(shardingValue.getValue());
for (String availableTarget : availableTargetNames){
if(availableTarget.endsWith(quarterMonth)){
return availableTarget;
}
}
throw new IllegalArgumentException("分表不存在,shardingValue="+quarterMonth);
}
}
b、 RangeShardingAlgorithm 實(shí)現(xiàn):(Range 處理 Between And 的路由)
public class DateRangeShardingAlgorithm implements RangeShardingAlgorithm<Date> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> shardingValue) {
Range<Date> dateRange = shardingValue.getValueRange();
Assert.notNull(dateRange.lowerEndpoint(), "分表鍵開(kāi)始時(shí)間不能為空");
Assert.notNull(dateRange.upperEndpoint(), "分表鍵截止時(shí)間不能為空");
Date start = DateTimeUtils.getStartOfMonth(dateRange.lowerEndpoint());
Date end = DateTimeUtils.getStartOfMonth(dateRange.upperEndpoint());
Collection<String> tables = new HashSet<String>();
for( Date date = start; date.compareTo(end) <= 0; date = DateUtil.addMonths(date, 1)){
String tableSuffix = DateShardingUtils.getYearQuarter(date);
for (String each : availableTargetNames) {
if (each.endsWith(tableSuffix)) {
tables.add(each);
break;
}
}
}
return tables;
}
}
如果有多個(gè)分片鍵的需求,如order_no,subject_no兩個(gè)分片鍵。我們希望其按照值的后兩位尾數(shù)進(jìn)行分表,并且表的區(qū)間為16,如表 ”table_00,table_16,table_32,table_48,...“。假如 order_no = 1000018,其會(huì)在表 t_order_16 表中。此時(shí)就需要使用ComplexShardingStrategy策略:
ComplexKeysShardingAlgorithm 實(shí)現(xiàn)
public class MyComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm {
private final String ORDER_NO = "order_no";
private final String SUBJECT_NO = "subject_no";
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, Collection<ShardingValue> shardingValues) {
ShardingValue shardingValue = null;
if( (shardingValue = getByColumnKey(shardingValues, ORDER_NO)) != null){
return getTableByNo(availableTargetNames, shardingValue);
}else if( (shardingValue = getByColumnKey(shardingValues, SUBJECT_NO)) != null){
return getTableByNo(availableTargetNames, shardingValue);
}
}
throw new IllegalArgumentException("Unsupported shardingValues: " + JSON.toJSONString(shardingValues));
}
private ShardingValue getByColumnKey(Collection<ShardingValue> shardingValues, final String columnKey){
for(ShardingValue shardingValue : shardingValues){
if(shardingValue.getColumnName().toLowerCase().equals(columnKey)){
return shardingValue;
}
}
return null;
}
/**
* 尾數(shù)即為分表位 算法
* 截取去最后2位分表位
* @param availableTargetNames
* @param shardingValue
* @return
*/
private Collection<String> getTableByNo(Collection<String> availableTargetNames, ShardingValue shardingValue){
if(shardingValue instanceof ListShardingValue){
Collection<String> noList = ((ListShardingValue) shardingValue).getValues();
Collection<String> tables = new HashSet<String>();
for(String no : noList){
//1、截取最后兩位
int tableIndex = Integer.valueOf(StringUtils.substring(no, no.length() - TsShardingConsts.TABLE_SEQ_LENGTH));
// 分表算法,16的倍數(shù)
int actualTableSeq = tableIndex / 16 * 16 ;
//3、查找分表
for (String each : availableTargetNames) {
if (each.endsWith(actualTableSeq)) {
tables.add(each);
break;
}
}
}
return tables;
}
throw new IllegalArgumentException("ShardingValue must be instanceof ListShardingValue.");
}
}
這里需要注意的是,= 和 in 的分片值都是ListShardingValue。
2.4 分片配置
基于 xml 的配置
- 分片規(guī)則配置 sharding-jdbc.xml
<sharding:complex-strategy id="myComplexKeysShardingAlgorithm" sharding-columns="order_no,subject_no"
algorithm-class="com.xxx.MyComplexKeysShardingAlgorithm" />
<sharding:standard-strategy id="repayDateTableStrategy" sharding-column="repay_date"
precise-algorithm-class="com.xxx.DatePreciseShardingAlgorithm"
range-algorithm-class="com.xxx.DateRangeShardingAlgorithm"/>
<sharding:data-source id="shardingDataSource">
<sharding:sharding-rule data-source-names="dataSource" default-data-source-name="dataSource">
<sharding:table-rules>
<sharding:table-rule logic-table="table" actual-data-nodes="dataSource.table_${[00,16,32,48,64,80,96]}"
table-strategy-ref="myComplexKeysShardingAlgorithm" />
<!-- 時(shí)間分表,按季度分: 2019q4 -->
<sharding:table-rule logic-table="table_date" actual-data-nodes="dataSource.table_20${[19,20,21,22]}q${[1,2,3,4]}"
table-strategy-ref="repayDateTableStrategy" />
</sharding:table-rules>
</beans>
- 數(shù)據(jù)源配置 jdbc.xml
<?xml version="1.0" encoding="UTF-8"?>
<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="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url">
<value>
${jdbc.druid.url}
</value>
</property>
<property name="username">
<value>${jdbc.druid.user}</value>
</property>
<property name="password">
<value>"${jdbc.druid.password}"</value>
</property>
<property name="filters">
<value>${jdbc.druid.filters}</value>
</property>
<property name="maxActive">
<value>${jdbc.druid.maxActive}</value>
</property>
<property name="initialSize">
<value>${jdbc.druid.initialSize}</value>
</property>
<property name="maxWait">
<value>${jdbc.druid.maxWait}</value>
</property>
<property name="minIdle">
<value>${jdbc.druid.minIdle}</value>
</property>
<property name="timeBetweenEvictionRunsMillis">
<value>${jdbc.druid.timeBetweenEvictionRunsMillis}</value>
</property>
<property name="minEvictableIdleTimeMillis">
<value>${jdbc.druid.minEvictableIdleTimeMillis}</value>
</property>
<property name="validationQuery">
<value>${jdbc.druid.validationQuery}</value>
</property>
<property name="testWhileIdle">
<value>${jdbc.druid.testWhileIdle}</value>
</property>
<property name="testOnBorrow">
<value>${jdbc.druid.testOnBorrow}</value>
</property>
<property name="testOnReturn">
<value>${jdbc.druid.testOnReturn}</value>
</property>
<property name="poolPreparedStatements">
<value>${jdbc.druid.poolPreparedStatements}</value>
</property>
<property name="maxOpenPreparedStatements">
<value>${jdbc.druid.maxOpenPreparedStatements}</value>
</property>
</bean>
<!-- myBatis文件 -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="mapperLocations" value="classpath*:META-INF/mybatis/mapper/*.xml"/>
<!-- 將 shrading-jdbc 作為數(shù)據(jù)源 -->
<property name="dataSource" ref="shardingDataSource"/>
</bean>
<import resource="classpath:META-INF/spring/sharding-jdbc.xml" />
</beans>
基于 Spring Boot 的配置
sharding.jdbc.datasource.names=ds
sharding.jdbc.datasource.ds.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.ds.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.ds.url=jdbc:mysql://localhost:3306/ds
sharding.jdbc.datasource.ds.username=root
sharding.jdbc.datasource.ds.password=123456
sharding.jdbc.config.sharding.tables.table(表名).logic-table=table
sharding.jdbc.config.sharding.tables.table.actual-data-nodes=ds.table_${[00,16,32,48,64,80,96]}
sharding.jdbc.config.sharding.tables.table.table-strategy.complex.sharding-columns=order_no,subject_no
sharding.jdbc.config.sharding.tables.table.table-strategy.complex.algorithm-class-name=com.xxx. MyComplexKeysShardingAlgorithm
sharding.jdbc.config.sharding.tables.table.logic-table=table_date
sharding.jdbc.config.sharding.tables.table.actual-data-nodes=ds.table_20${[19,20,21,22]}q${[1,2,3,4]}
sharding.jdbc.config.sharding.tables.t_order_item.table-strategy.inline.sharding-column=repay_date
sharding.jdbc.config.sharding.tables.table.table-strategy.standard.precise-algorithm-class-name=com.xxx.DatePreciseShardingAlgorithm
sharding.jdbc.config.sharding.tables.table.table-strategy.standard.range-algorithm-class-name=com.xxx. DateRangeShardingAlgorithm
至此,Sharding-JDBC 的配置已經(jīng)講完,同學(xué)們可以自己實(shí)踐一下,相對(duì)來(lái)說(shuō)很是很簡(jiǎn)單的。后面,我們將深入源碼,了解其底層的實(shí)現(xiàn)機(jī)制,盡請(qǐng)關(guān)注!