文章目录
前言
上篇文章主要使用SpringBoot+Mybatis-Plus+Sharding-JDBC实现了简单的分表,本篇来讲下Sharding-JDBC提供的几种分表策略,通过使用不同的策略可以实现各种复杂的分表场景
一、分片策略是什么?
包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供5种分片策略。接下来逐一讲解使用方式
二、使用
1.行表达式分片策略
对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0到t_user_7。
上篇文章我们已经使用了行表达式分片策略来进行分表操作了,具体使用方式也很简单,只需要简单的配置即可:
t_user_order:
key-generator-column-name: id #需要自动生成的字段,使用雪花算法生成
actual-data-nodes: ds0.t_user_order${0..2}
#数据节点,即要对目标表进行几个节点的拆分,
#${0..2} 为Groovy语法,
#t_user_order${0..2}解析后为t_user_order0、t_user_order1、t_user_order2三张表
table-strategy: #分表策略
inline: #行表达式策略
sharding-column: id #分表键 即通过该字段来分表
algorithm-expression: t_user_order${id % 3} #算法表达式,这通过对id求余来进行分表
2.标准分片策略
对应StandardShardingStrategy。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。
配置
t_user: #t_user表
key-generator-column-name: id #主键
actual-data-nodes: ds0.t_user${0..1} #数据节点,均匀分布
table-strategy: #分表策略
standard: #标准分片策略
sharding-column: sex #分片字段
#分片算法
precise-algorithm-class-name: com.none.sharding.infrastruc.shardingAlgorithm.UserShardingAlgorithm
UserShardingAlgorithm
public class UserShardingAlgorithm implements PreciseShardingAlgorithm<Long>, RangeShardingAlgorithm<Long> {
/**
* 精确分片算法
* 这里我们可以实现自己的分片算法
* @param collection 实际分表的节点 比如t_user0\t_user1
* @param preciseShardingValue
* 分片值对象,包括逻辑表名即原表名t_user; 分片键、分片键的值
* @return 返回实际表名即t_user0
*/
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
System.out.println("--------------preciseShardingValue" + preciseShardingValue.getColumnName());
System.out.println("--------------collection" + collection);
return preciseShardingValue.getLogicTableName() + String.valueOf(preciseShardingValue.getValue()%2);
}
/**
* 范围分片算法
* 默认将使用全路由即表节点有几个就会去查几张表,
* 然后对表结果进行聚合返回,性能较差
*
* @param availableTargetNames available data sources or tables's names
* @param shardingValue sharding value
* @return sharding results for data sources or tables's names
*/
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> shardingValue) {
return availableTargetNames;
}
}
3.复合分片策略
对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。
这里有个场景,比如我们发现公司的订单数据量每年递增基本都在1000W的数据量,如果我们只通过id进行拆分表,按照500W来拆分的话,,其实只需要两张表即可,但是随着数据量的逐年递增,每年都有1000W的数据量进来,那么很难对表进行一个合理的拆分,要么一下子拆分几十张满足几年的数据量,要么到时再进行伸缩,其实都不是一个较好的方案。这时候我们可以通过年份+user_id来组合拆分,即每年都拆分出两张表,看如下实现
配置
t_user_order:
key-generator-column-name: id #主键
#数据节点,数据库中t_user_order20200,t_user_order20201,t_user_order20202
# #数据节点,数据库中t_user_order20210,t_user_order20211,t_user_order20212
actual-data-nodes: ds0.t_user_order${2020..2021}${0..2} #数据节点,均匀分布
table-strategy: #分表策略
complex: #复合策略
#通过userId、createTime组合分片
sharding-columns: user_id,create_time
algorithm-class-name: com.none.sharding.infrastruc.shardingAlgorithm.ComplexShardingAlgorithm
ComplexShardingAlgorithm
public class ComplexShardingAlgorithm
implements ComplexKeysShardingAlgorithm {
/**
* 复合分片算法
* 分片键之间的关系通常都比较复杂,需要考虑in、<=、>、between
* 等范围查询的场景下的分片规则
* 这里只做个简单demo 实际场景还需要慎重考虑
*
* @param availableTargetNames available data sources or tables's names
* @param shardingValue sharding value
* @return sharding results for data sources or tables's names
*/
@Override
public Collection<String> doSharding(Collection availableTargetNames, ComplexKeysShardingValue shardingValue) {
//范围分片
Map<String, Range<String>> rangeAndShardingValuesMap =
shardingValue.getColumnNameAndRangeValuesMap();
HashSet<Integer> yearSet = new HashSet<>();
HashSet<Long> userIdSet = new HashSet<>();
//时间作为范围查询
if (rangeAndShardingValuesMap.containsKey("create_time")) {
Range<String> createTimeRange = rangeAndShardingValuesMap.get("create_time");
String lowerTime = "2020-01-01 00:00:00";
if (createTimeRange.hasLowerBound()) {
lowerTime = createTimeRange.lowerEndpoint();
}
yearSet.add(LocalDateTime.parse(String.valueOf(lowerTime),
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).getYear());
String upperTime = "2021-01-01 00:00:00";
if (createTimeRange.hasUpperBound()) {
upperTime = createTimeRange.upperEndpoint();
}
yearSet.add(LocalDateTime.parse(String.valueOf(upperTime),
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).getYear());
} else {
//如果查询条件没有createTimed则执行全路由
yearSet.add(2020);
yearSet.add(2021);
}
//精确分片
Map<String, Collection<Long>> columnNameAndShardingValuesMap =
shardingValue.getColumnNameAndShardingValuesMap();
//create_time 精确分片
if (columnNameAndShardingValuesMap.containsKey("create_time")) {
yearSet.clear();
for (Object value : columnNameAndShardingValuesMap.get("create_time")) {
yearSet.add(((LocalDateTime) value).getYear());
}
}
//user_id作为精确分片
if (columnNameAndShardingValuesMap.containsKey("user_id")) {
for (Object value : columnNameAndShardingValuesMap.get("user_id")) {
userIdSet.add((long) value % 3);
}
} else {
//如果查询条件没有userId则执行全路由
userIdSet.add(0L);
userIdSet.add(1L);
userIdSet.add(2L);
}
ArrayList<String> actualTables = new ArrayList<>();
for (Integer year : yearSet) {
for (Long uidMod : userIdSet) {
actualTables.add(shardingValue.getLogicTableName() + year + uidMod);
}
}
return actualTables;
}
}```
示例:通过用户id和时间查询订单
```java
/**
* 查询订单
*
* @param uid
* @return
*/
@RequestMapping("getOne")
public UserOrderDO getOneOrder(@RequestParam Long uid,
@RequestParam String dateTimeStart,
@RequestParam String dateTimeEnd) {
QueryWrapper<UserOrderDO> addressDOQueryWrapper = new QueryWrapper<>();
addressDOQueryWrapper.lambda()
.eq(UserOrderDO::getUserId, uid)
.gt(UserOrderDO::getCreateTime, dateTimeStart)
.lt(UserOrderDO::getCreateTime, dateTimeEnd);
return userOrderService.getOne(addressDOQueryWrapper);
}
生成的sql如下:
Logic SQL: SELECT id,order_no,user_id,amount,create_time FROM t_user_order WHERE (user_id = ? AND create_time > ? AND create_time < ?)
## 实际sql会路由到t_user_order20201表
Actual SQL: ds0 ::: SELECT id,order_no,user_id,amount,create_time FROM t_user_order20201
WHERE (user_id = ? AND create_time > ? AND create_time < ?) ::: [1, 2020-01-01 00:00:00, 2020-11-01 00:00:00]
总结
以上对Sharding-JDBC的三种分片策略进行了简单介绍,由于Hint分片策略稍微复杂些,我们下篇文章专门来介绍
DEMO地址:https://github.com/nangge/sharding.git