前言
因项目热门业务数据爆增,Mysql单表扛不住持续暴增数据量,集成使用Shardingsphere进行水平分表分库。这里记录下。
项目版本
springboot 2.7.5
shardingsphere-jdbc-core-spring-boot-starter 5.2.0
jdk 17
步骤一
pom.xml添加依赖
<!-- ShardingSphere JDBC Core -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.2.0</version>
</dependency>
步骤二
yml配置
spring:
shardingsphere:
mode:
type: Standalone # Standalone=单机模式或 Cluster、Memory
datasource:
names: ds
ds:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: root
rules:
sharding:
tables:
dp_im_users_info:
# 定义实际的数据节点范围。这里表示 `dp_im_users_info` 表的数据会根据年份(2025 到 2035)和月份(1 -12 月)分布在不同的节点上
actual-data-nodes: ds.dp_im_users_info_$->{202501..203512}
table-strategy:
standard:
# 指定分片列为 `create_time`,即根据 `create_time` 列的值进行数据分片
sharding-column: create_time
# 指定分片算法的名称,这里使用之前定义的 `tba-monthly`,表示按月进行分片
sharding-algorithm-name: tba-monthly
sharding-algorithms:
# 自定义分库分表的算法 这里实现的是按年月份-水平分表不分库
# tba-monthly:
# type: CLASS_BASED
# props:
# strategy: STANDARD
# algorithmClassName: com.shardingsphere.CustomTableShardingAlgorithm#直接配置按年月份-水平分表不分库
tba-monthly: #sharding-algorithm-name配置的名称
type: INTERVAL # 指定分片策略的类型为基于时间区间的分片策略
props:
datetime-pattern: yyyy-MM-dd HH:mm:ss # 定义时间格式,表示日期和时间的格式为年-月-日 时:分:秒
datetime-lower: 2025-01-01 00:00:00 # 设置分片的开始时间,即时间范围的下限
datetime-upper: 2035-12-31 23:59:59 # 设置分片的结束时间,即时间范围的上限
sharding-suffix-pattern: yyyyMM # 定义生成分片表后缀的时间格式,这里是按年和月进行分片(如 202301, 202302 等)
datetime-interval-amount: 1 # 定义分片的时间间隔大小,这里是每个间隔为 1(即每个月创建一个分片)
datetime-interval-unit: MONTHS # 指定时间间隔的单位,这里为按月分片
props:
sql-show: true # 开启 SQL 打印,便于调试
扩展说明:
第一点:Shardingsphere是不会自动创建水平分表的需要自己手动创建\预创建
如这里配置水平分表时间为202501-203512 ,当insert创建时间字段会自动添加到对应时间点的表中。不会自动创建对应水平分表,如果未创建执行sql会报找不到表错误
第二点:Shardingsphere按create_time字段分表,查询未指定create_time范围,会直接检索所有水平表
如:查询202501-203512所有表,如果有存在未创建的表会直接执行报错
第三点:Shardingsphere需要处理历史数据
方法一:从源数据表按时间归类到对应时间分片表中
方法二:自定义分库分表的算法 可以设置时间节点前查哪个表 时间节点后查哪个分片表
注:查询未指定create_time范围,会直接检索所有水平表是不会触CustomTableShardingAlgorithm的实现方法 ,指定了create_time范围才会触发
yml配置
shardingsphere: mode: type: Standalone # Standalone=单机模式或 Cluster、Memory datasource: names: ds ds: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: root rules: sharding: tables: dp_im_users_info: # 定义实际的数据节点范围。这里表示 `dp_im_users_info` 表的数据会根据年份(2025 到 2035)和月份(1 -12 月)分布在不同的节点上 actual-data-nodes: ds.dp_im_users_info_$->{202501..203512} table-strategy: standard: # 指定分片列为 `create_time`,即根据 `create_time` 列的值进行数据分片 sharding-column: create_time # 指定分片算法的名称,这里使用之前定义的 `tba-monthly`,表示按月进行分片 sharding-algorithm-name: tba-monthly sharding-algorithms: # 自定义分库分表的算法 这里实现的是按年月份-水平分表不分库 tba-monthly: type: CLASS_BASED props: strategy: STANDARD algorithmClassName: com.shardingsphere.CustomTableShardingAlgorithm
CustomTableShardingAlgorithm.java
import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Range;
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.SimpleDateFormat;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
public class CustomTableShardingAlgorithm implements StandardShardingAlgorithm<Date> {
private static final DateTimeFormatter TABLE_FORMAT = DateTimeFormatter.ofPattern("yyyyMM");
private static final Date MIN_VALUE = Date.from(YearMonth.parse("202503", java.time.format.DateTimeFormatter.ofPattern("yyyyMM"))
.atDay(1).atStartOfDay(ZoneId.of("UTC")) // 指定时区(可替换为 ZoneId.systemDefault())
.toInstant());
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) {
// 获取分片键值(create_time)
Date createTime = shardingValue.getValue();
Date now = new Date();
if (createTime.before(MIN_VALUE)) {
createTime = MIN_VALUE;
}
else if (createTime.after(now)) {
createTime = now;
}
// 生成表后缀:_202501
String tableSuffix = DateUtil.format(createTime, TABLE_FORMAT);
// 组合逻辑表名 + 后缀
return shardingValue.getLogicTableName() + "_" + tableSuffix;
}
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Date> rangeShardingValue) {
Range<Date> range = rangeShardingValue.getValueRange();
Date start = range.hasLowerBound() ? range.lowerEndpoint() : MIN_VALUE;
if (start.before(MIN_VALUE)) {
start = MIN_VALUE;
}
Date end = new Date();// range.hasUpperBound() ? range.upperEndpoint() : new Date();
Set<String> tables = new HashSet<>();
while (start.before(end)) {
tables.add(rangeShardingValue.getLogicTableName() + "_" + DateUtil.format(start, TABLE_FORMAT));
start = addMonth(start, 1);
}
return tables;
}
@Override
public Properties getProps() {
return new Properties();
}
@Override
public void init(Properties properties) {
// 初始化逻辑(可留空)
System.out.println("===============================");
}
/**
* 返回日期加X月后的日期
*/
public static Date addMonth(Date date, int i) {
try {
String dateStr = DateUtil.format(date,sdf);
GregorianCalendar gCal = new GregorianCalendar(Integer.parseInt(dateStr.substring(0, 4)),
Integer.parseInt(dateStr.substring(5, 7)) - 1, Integer.parseInt(dateStr.substring(8, 10)));
gCal.add(GregorianCalendar.MONTH, i);
return gCal.getTime();
} catch (Exception e) {
return null;
}
}
}
完结撒花!!!
各位侠士就是我的动力,助力下,来个赞吧感谢!!!
各位侠士就是我的动力,助力下,来个赞吧感谢!!!
各位侠士就是我的动力,助力下,来个赞吧感谢!!