参考资料
ShardingSphere5.x.x(最新版)配置MySQL读写分离
ShardingSphere笔记(二):自定义分片算法 — 按月分表
Spring Boot集成ShardingSphere实现按月数据分片及创建自定义分片算法 | Spring Cloud 44
升级到ShardingSphere5后,原有的ShardingSphere4配置需要做出一些变更,这里特意记录一下方便其他有需要的朋友们快速切换。
我是一个从前端转过来,所以目前追求的是如何使用,至于原理可以看上面的参考资料,里面讲的比价详细。
一、pom依赖
升级前 ShardingSphere4需要添加的pom依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
升级后ShardingSphere5需要添加的pom依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.2.1</version>
</dependency>
<!--
这里一定要设置yml版本,否则在shardingsphere 5.2 版本及以上会出现如下问题:
"Correct the classpath of your application so that it contains a single, compatible version of org.apache.shardingsphere.infra.
util.yaml.constructor.ShardingSphereYamlConstructor$1"
因为需要的解析yaml文件的库版本不满足框架要求,根据不同的springboot版本做出调整。
我这里使用的是springboot 2.6.8, 需要手动依赖`snakeyarml`并指定版本才可以。
如果你使用的是更高版本的springboot,pom文件中的properties位置调整yaml版本号即可。Springboot框架自带了这个框架,不需要重新引用
具体以你使用的springboot 版本为准进行调整就好了。
-->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
二、自定义分配算法
package com.study.config.shardingsphere;
import cn.hutool.core.date.DateUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 按月分表的 Sharding 算法
*/
@Getter
@Slf4j
public class DataMonthShardingAlgorithm implements StandardShardingAlgorithm<Date> {
private final ThreadLocal<SimpleDateFormat> formatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMM"));
private Properties props;
/**
* 设置该参数的原因是,如果在范围查找的时候我们没有设置最小值,比如下面的查询
* where _time < '2023-09-01 00:00:00'
* 这个时候范围查找就只有上限而没有下限,这时候就需要有一个下限值兜底,不能一致遍历下去
*/
private Date tableLowerDate;
/**
* 在配置文件中配置算法的时候会配置 props 参数,框架会将props中的配置放在 properties 参数中,并且初始化算法的时候被调用
*
* @param properties properties
*/
@Override
public void init(Properties properties) {
this.props = properties;
String autoCreateTableLowerDate = properties.getProperty("auto-create-table-lower");
try {
this.tableLowerDate = formatThreadLocal.get().parse(autoCreateTableLowerDate);
} catch (Exception e) {
log.error("parse auto-create table lower date failed: {}, use default date 2023-09", e.getMessage());
try {
this.tableLowerDate = formatThreadLocal.get().parse("202309");
} catch (ParseException ignored) {
}
}
}
/**
* 精确路由算法
*
* @param availableTargetNames 可用的表列表(配置文件中配置的 actual-data-nodes会被解析成 列表被传递过来)
* @param shardingValue 精确的值
* @return 结果表
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) {
Date value = shardingValue.getValue();
// 根据精确值获取路由表
String actuallyTableName = shardingValue.getLogicTableName() + shardingSuffix(value);
if (availableTargetNames.contains(actuallyTableName)) {
return actuallyTableName;
}
return null;
}
/**
* 范围路由算法
*
* @param availableTargetNames 可用的表列表(配置文件中配置的 actual-data-nodes会被解析成 列表被传递过来)
* @param shardingValue 值范围
* @return 路由后的结果表
*/
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> shardingValue) {
// 获取到范围查找的最小值,如果条件中没有最小值设置为 tableLowerDate
Date rangeLowerDate;
if (shardingValue.getValueRange().hasLowerBound()) {
rangeLowerDate = shardingValue.getValueRange().lowerEndpoint();
} else {
rangeLowerDate = tableLowerDate;
}
// 获取到范围查找的最大值,如果没有配置最大值,设置为当前时间 + 1 月
// 这里需要注意,你的项目里面这样做是否合理
Date rangeUpperDate;
if (shardingValue.getValueRange().hasUpperBound()) {
rangeUpperDate = shardingValue.getValueRange().upperEndpoint();
} else {
// 往后延一个月
rangeUpperDate = DateUtil.offsetMonth(new Date(), 1);
}
rangeUpperDate = DateUtil.endOfMonth(rangeUpperDate);
List<String> tableNames = new ArrayList<>();
// 过滤那些存在的表
while (rangeLowerDate.before(rangeUpperDate)) {
String actuallyTableName = shardingValue.getLogicTableName() + shardingSuffix(rangeLowerDate);
if (availableTargetNames.contains(actuallyTableName)) {
tableNames.add(actuallyTableName);
}
rangeLowerDate = DateUtil.offsetMonth(rangeLowerDate, 1);
}
return tableNames;
}
/**
* sharding 表后缀 _yyyyMM
*/
private String shardingSuffix(Date shardingValue) {
return "_" + formatThreadLocal.get().format(shardingValue);
}
/**
* SPI方式的 SPI名称,配置文件中配置的时候需要用到
*/
@Override
public String getType() {
return "HIS_DATA_SPI_BASED";
}
}
三、注册算法
这里我们采用SPI的方式将自定义算法注册到ShardingSphere。
首先需要在 resources 文件夹下创建文件夹 META-INF/services。然后在该文件夹下创建文件,
文件名为org.apache.shardingsphere.sharding.spi.ShardingAlgorithm
,这个文件名不能随意修改,否则无法注册成功, 文件内写上我们自定义的分片算法类全路径,也就是上面的com.study.config.shardingsphere.DataMonthShardingAlgorithm
。
四、yml配置文件
升级前 ShardingSphere4需要添加的数据库配置, ShardingSphere4 我没有做过按月分表,还是使用的根据id取模分表,后面需要做按月分表了才升级到了ShardingSphere5。
spring:
shardingsphere:
props:
sql:
show: false
datasource:
type: com.alibaba.druid.pool.DruidDataSource
names: db-0
db-0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2b8&rewriteBatchedStatements=true
sharding:
default-data-source-name: db-0
tables:
person_sync_record:
actual-data-nodes: db-0.person_sync_record_$->{0..15}
key-generator:
column: equipment_id
type: SNOWFLAKE
table-strategy:
inline:
sharding-column: equipment_id
algorithmExpression: person_sync_record_$->{equipment_id % 16}
track_record:
actual-data-nodes: db-0.track_record_$->{0..1}
key-generator:
column: id
type: SNOWFLAKE
table-strategy:
inline:
sharding-column: id
algorithmExpression: track_record_$->{id % 2}
升级前 ShardingSphere5需要添加的数据库配置
spring:
shardingsphere:
props:
sql:
show: false
datasource:
names: db-0
db-0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2b8&rewriteBatchedStatements=true
rules:
sharding:
tables:
person_sync_record:
actual-data-nodes: db-0.person_sync_record_$->{0..15}
key-generator:
column: id
type: SNOWFLAKE
table-strategy:
standard:
sharding-column: id
sharding-algorithm-name: person_sync_record_inline
track_record:
# 表的真实表列表(需要注意这里的表都应该存在,否则就有可能出现 NPE 异常)
actual-data-nodes: db-0.track_record_202309, db-0.track_record_2023${10..12}, db-0.track_record_20240${1..9}, db-0.track_record_2024${10..12}
key-generator:
column: id
type: SNOWFLAKE
# 分片策略配置
table-strategy:
standard:
# 分片算法是 his_month_sharding, 这个名称是在下面配置的
sharding-algorithm-name: his_month_sharding
# 分片列是 trace_time, 需要注意的是,分表算法的数据类型一定要和这个分片的列数据类型一致
sharding-column: trace_time
# 配置分片算法
sharding-algorithms:
person_sync_record_inline:
type: INLINE
props:
algorithm-expression: person_sync_record_$->{id % 16}
# 分片算法名称
his_month_sharding:
# 分片算法类型,这个type是我们的分片算法实现类中 getType()的返回值,SPI适用于这种方式
type: HIS_DATA_SPI_BASED
五、测试
运行项目,对两个表分别插入数据,然后进行查询发现数据正常。这里需要注意的是按月分表的 数据表进行查询的时候一定要带上 时间,否则无法确定你需要查询的分表是哪些。