1. 简介
Sharding-JDBC 是当当开源的数据库分库分表中间件,同时也支持读写分离。
Sharding-JDBC 定位为轻量级 java 框架,使用客户端直连数据库,以 jar 包形式提供服务,未使用中间层,无需额外部署,无其他依赖,DBA 也无需改变原有的运维方式,可理解为增强版的 JDBC 驱动,旧代码迁移成本几乎为零。
Sharding-JDBC 架构的核心逻辑为分片规则配置、SQL解析、SQL路由、SQL改写、SQL执行以及结果归并模块。
Sharding-JDBC 架构图
2. 客户端配置
2.1 添加 maven 依赖
com.dangdang
sharding-jdbc-core
2.0.3
2.2 分片算法
Sharding-JDBC 提供了以下 5 种分片策略,都继承至ShardingStrategy:
StandardShardingStrategy
标准分片策略(最常用)。提供对 SQL语 句中的 =, IN 和 BETWEEN AND的分片操作支持。
StandardShardingStrategy 只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。
PreciseShardingAlgorithm 是必选的,用于处理 = 和 IN 的分片。
RangeShardingAlgorithm 是可选的,用于处理 BETWEEN AND 分片,如果不配置RangeShardingAlgorithm,SQL中的 BETWEEN AND 将按照全库路由处理。
ComplexShardingStrategy
复合分片策略。提供对 SQL 语句中的 =, IN 和 BETWEEN AND 的分片操作支持。
ComplexShardingStrategy 支持多分片键,由于多分片键之间的关系复杂,因此 Sharding-JDBC 并未做过多的封装,而是直接将分片键值组合以及分片操作符交于算法接口,完全由应用开发者实现,提供最大的灵活度。
InlineShardingStrategy
Inline 表达式分片策略。使用 Groovy 的 Inline 表达式,提供对 SQL 语句中的 = 和 IN 的分片操作支持。
InlineShardingStrategy 只支持单分片键,对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的 Java 代码开发,如: tuser${user_id % 8} 表示 t_user 表按照 user_id 对 8 取模分成 8 个表,表名称为 t_user_0 到 t_user_7。
HintShardingStrategy
通过 Hint 而非 SQL 解析的方式分片的策略。
NoneShardingStrategy
不分片的策略。
2.3 自定义分片算法
Sharding-JDBC 提供了以下4种算法接口:
PreciseShardingAlgorithm
RangeShardingAlgorithm
HintShardingAlgorithm
ComplexKeysShardingAlgorithm
现在我们有个分表需求,将时间字段,如repay_date(yyyy-MM-dd)按照季度分表,例如:“table_2019q1,table_2019q2,table_2019q3,table_2019q4,...”。由于时间的比较方式通常为区间比较,于是我们可以采用StandardShardingStrategy下的分片策略。
a、PreciseShardingAlgorithm 实现:(Precise 处理 = 和 in 的路由)
public class DatePreciseShardingAlgorithm implements PreciseShardingAlgorithm {
@Override
public String doSharding(Collection availableTargetNames, PreciseShardingValue 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 实现:(Range 处理 Between And 的路由)
public class DateRangeShardingAlgorithm implements RangeShardingAlgorithm {
@Override
public Collection doSharding(Collection availableTargetNames, RangeShardingValue shardingValue) {
Range dateRange = shardingValue.getValueRange();
Assert.notNull(dateRange.lowerEndpoint(), "分表键开始时间不能为空");
Assert.notNull(dateRange.upperEndpoint(), "分表键截止时间不能为空");
Date start = DateTimeUtils.getStartOfMonth(dateRange.lowerEndpoint());
Date end = DateTimeUtils.getStartOfMonth(dateRange.upperEndpoint());
Collection tables = new HashSet();
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;
}
}
如果有多个分片键的需求,如order_no,subject_no两个分片键。我们希望其按照值的后两位尾数进行分表,并且表的区间为16,如表 ”table_00,table_16,table_32,table_48,...“。假如 order_no = 1000018,其会在表 t_order_16 表中。此时就需要使用ComplexShardingStrategy策略:
ComplexKeysShardingAlgorithm 实现
public class MyComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm {
private final String ORDER_NO = "order_no";
private final String SUBJECT_NO = "subject_no";
@Override
public Collection doSharding(Collection availableTargetNames, Collection 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 shardingValues, final String columnKey){
for(ShardingValue shardingValue : shardingValues){
if(shardingValue.getColumnName().toLowerCase().equals(columnKey)){
return shardingValue;
}
}
return null;
}
/**
* 尾数即为分表位 算法
* 截取去最后2位分表位
* @param availableTargetNames
* @param shardingValue
* @return
*/
private Collection getTableByNo(Collection availableTargetNames, ShardingValue shardingValue){
if(shardingValue instanceof ListShardingValue){
Collection noList = ((ListShardingValue) shardingValue).getValues();
Collection tables = new HashSet();
for(String no : noList){
//1、截取最后两位
int tableIndex = Integer.valueOf(StringUtils.substring(no, no.length() - TsShardingConsts.TABLE_SEQ_LENGTH));
// 分表算法,16的倍数
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 的配置
分片规则配置 sharding-jdbc.xml
algorithm-class="com.xxx.MyComplexKeysShardingAlgorithm" />
precise-algorithm-class="com.xxx.DatePreciseShardingAlgorithm"
range-algorithm-class="com.xxx.DateRangeShardingAlgorithm"/>
table-strategy-ref="myComplexKeysShardingAlgorithm" />
table-strategy-ref="repayDateTableStrategy" />
数据源配置 jdbc.xml
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">
${jdbc.druid.url}
${jdbc.druid.user}
"${jdbc.druid.password}"
${jdbc.druid.filters}
${jdbc.druid.maxActive}
${jdbc.druid.initialSize}
${jdbc.druid.maxWait}
${jdbc.druid.minIdle}
${jdbc.druid.timeBetweenEvictionRunsMillis}
${jdbc.druid.minEvictableIdleTimeMillis}
${jdbc.druid.validationQuery}
${jdbc.druid.testWhileIdle}
${jdbc.druid.testOnBorrow}
${jdbc.druid.testOnReturn}
${jdbc.druid.poolPreparedStatements}
${jdbc.druid.maxOpenPreparedStatements}
基于 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 的配置已经讲完,同学们可以自己实践一下,相对来说很是很简单的。后面,我们将深入源码,了解其底层的实现机制,尽请关注!