Springboot 集成 Shardingsphere-JDBC
Shardingsphere系列目录:
【Springboot 集成 Shardingsphere-JDBC】
【Shardingsphere-Proxy 5.5.0部署】
背景
项目中,某数据库单表数据量已达到4000w+,查询和插入数据性能越来越差,跟踪发现这个表一个月有8w的新增数据量,所以需要进行分库分表减轻单库单表的压力。
调研
- 为降低学习成本和时间成本,其他项目已经用shardingsphere实现分库分表,所以可以拿来借鉴一下。
- 使用shardingsphere无非就是分库、分表,配置复杂度根据业务复杂度的提升而提升。
前提
Shardingsphere不会为你自动建库建表,需要自行将分开的表以及对应数据库全部创建出来。
新增依赖
有两个依赖提供给大家
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.2.1</version>
</dependency>
以上两个依赖只能加一个,建议使用后者,因为毕竟差着1个大版本,并且,前者最高版本就是4.1.1,看样子好像是不会再维护了。
分表策略
简单分库分表策略
垂直分库
# 垂直分库
spring:
application:
name: sharding-jdbc
shardingsphere:
datasource:
# 数据源别名
db1:
# 数据库连接信息
driver-class-name: com.mysql.jdbc.Driver
password: 1234
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:mysql://localhost:3306/sharding_payorder_db?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
username: root
# 数据源别名
db2:
# 数据库连接信息
driver-class-name: com.mysql.jdbc.Driver
password: 1234
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:mysql://localhost:3306/sharding_user_db?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
username: root
# 数据源别名列表
names: db1,db2
props:
# 展示执行sql
sql-show: true
rules:
sharding:
# 标准分片表配置,由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
tables:
pay_order:
actual-data-nodes: db1.pay_order
users:
actual-data-nodes: db2.users
广播表
# 广播表
spring:
application:
name: sharding-jdbc
shardingsphere:
datasource:
# 数据源别名
db:
# 数据库连接信息
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/sharding_course_db?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
password: 1234
type: com.zaxxer.hikari.HikariDataSource
username: root
# 数据源别名
db0:
# 数据库连接信息
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/sharding_course_db0?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
password: 1234
type: com.zaxxer.hikari.HikariDataSource
username: root
# 数据源别名
db1:
# 数据库连接信息
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/sharding_course_db1?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
password: 1234
type: com.zaxxer.hikari.HikariDataSource
username: root
# 数据源别名列表
names: db0,db1,db
props:
sql-show: true
rules:
sharding:
# 广播表配置
broadcast-tables:
- t_district
tables:
t_district:
# 广播表设置,定位广播表所在数据源
actual-data-nodes: db$->{0..1}.t_district,db.t_district
水平分库(单表)
# 水平分库(单表)
spring:
application:
name: sharding-jdbc
shardingsphere:
datasource:
# 数据源别名
db0:
# 数据库连接信息
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/sharding_course_db0?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
password: 1234
type: com.zaxxer.hikari.HikariDataSource
username: root
# 数据源别名
db1:
# 数据库连接信息
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/sharding_course_db1?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
password: 1234
type: com.zaxxer.hikari.HikariDataSource
username: root
# 数据源别名列表
names: db0,db1
props:
sql-show: true
rules:
sharding:
# 主键生成算法定义
key-generators:
# 主键生成算法定义名称
alg-snowflake:
# 对应主键生成算法
type: SNOWFLAKE
# 分片算法定义
sharding-algorithms:
# 分片算法定义名称
inline-hash-mod:
props:
# 分片算法表达式
algorithm-expression: t_course_$->{Math.abs(cid.hashCode()) % 2}
# 分片算法类型,行表达式分片算法
type: INLINE
# 分片算法定义名称
table-inline:
props:
# 分片算法表达式
algorithm-expression: db$->{user_id % 2}
# 分片算法类型,行表达式分片算法
type: INLINE
tables:
t_course:
# 配置表节点
actual-data-nodes: db$->{0..1}.t_course_$->{0..1}
# 数据库分片策略
database-strategy:
standard:
# 数据库分片策略名称,在上面的 sharding-algorithms 内容中配置
sharding-algorithm-name: table-inline
# 数据库分片列名称
sharding-column: user_id
# t_course表主键生成策略
key-generate-strategy:
# 主键列名称
column: cid
# 主键生成算法名称,在上面的 key-generators 内容中配置
key-generator-name: alg-snowflake
# 表分片策略
table-strategy:
standard:
# 表分片策略名称,在上面的 sharding-algorithms 内容中配置
sharding-algorithm-name: inline-hash-mod
# 表分片列名称
sharding-column: cid
水平分库(多表)
# 水平分库(多表)
spring:
application:
name: sharding-jdbc
shardingsphere:
datasource:
# 数据源别名
db0:
# 数据库连接信息
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/sharding_course_db0?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
password: 1234
type: com.zaxxer.hikari.HikariDataSource
username: root
# 数据源别名
db1:
# 数据库连接信息
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/sharding_course_db1?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
password: 1234
type: com.zaxxer.hikari.HikariDataSource
username: root
# 数据源别名列表
names: db0,db1
props:
sql-show: true
rules:
sharding:
# 绑定表配置
binding-tables:
- t_course,t_course_section
# 主键生成算法定义
key-generators:
# 主键生成算法定义名称
alg-snowflake:
# 对应主键生成算法
type: SNOWFLAKE
# 分片算法定义
sharding-algorithms:
# 分片算法定义名称
table-hash-mod:
props:
# 指定分片数量
sharding-count: 2
# 哈希取模分片算法
type: HASH_MOD
# 分片算法类型,分片算法定义名称
table-mod:
props:
# 指定分片数量
sharding-count: 2
# 片算法类型,取模分片算法
type: MOD
tables:
t_course:
# 配置表节点
actual-data-nodes: db$->{0..1}.t_course_$->{0..1}
# 数据库分片策略
database-strategy:
standard:
# 数据库分片策略名称,在上面的 sharding-algorithms 内容中配置
sharding-algorithm-name: table-mod
# 数据库分片列名称
sharding-column: user_id
# t_course表主键生成策略
key-generate-strategy:
# 主键列名称
column: cid
# 主键生成算法名称,在上面的 key-generators 内容中配置
key-generator-name: alg-snowflake
# 表分片策略
table-strategy:
standard:
# 表分片策略名称,在上面的 sharding-algorithms 内容中配置
sharding-algorithm-name: table-mod
# 表分片列名称
sharding-column: corder_no
t_course_section:
# 配置表节点
actual-data-nodes: db$->{0..1}.t_course_section_$->{0..1}
# 数据库分片策略
database-strategy:
standard:
# 数据库分片策略名称,在上面的 sharding-algorithms 内容中配置
sharding-algorithm-name: table-mod
# 数据库分片列名称
sharding-column: user_id
# t_course_section表主键生成策略
key-generate-strategy:
# 主键列名称
column: id
# 主键生成算法名称,在上面的 key-generators 内容中配置
key-generator-name: alg-snowflake
# 表分片策略
table-strategy:
standard:
# 表分片策略名称,在上面的 sharding-algorithms 内容中配置
sharding-algorithm-name: table-mod
# 表分片列名称
sharding-column: corder_no
水平分表
# 水平分表
spring:
application:
name: sharding-jdbc
shardingsphere:
datasource:
# 数据源别名
db1:
# 数据库连接信息
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/sharding_course_db?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
password: 1234
type: com.zaxxer.hikari.HikariDataSource
username: root
tdms:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/jvs_tdms?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
username: root
password: 1234
# 数据源别名列表
names: db1,tdms
# 未分片数据库,如果分片数据库也有未分片的表,会优先查分片数据库的这个表,否则去查默认的未分片数据库
sharding:
default-data-source-name: tdms
props:
sql-show: true
rules:
sharding:
# 主键生成算法定义
key-generators:
# 主键生成算法定义名称
alg-snowflake:
# 对应主键生成算法
type: SNOWFLAKE
# 分片算法定义
sharding-algorithms:
# 分片算法定义名称
table-inline:
props:
# 分片算法表达式
algorithm-expression: t_course_$->{cid % 2 + 1}
# 分片算法类型,行表达式分片算法
type: INLINE
tables:
t_course:
# 配置表节点
actual-data-nodes: db1.t_course_$->{1..2}
# t_course表主键生成策略
key-generate-strategy:
# 主键列名称
column: cid
# 主键生成算法名称,在上面的 key-generators 内容中配置
key-generator-name: alg-snowflake
table-strategy:
standard:
# 表分片策略名称,在上面的 sharding-algorithms 内容中配置
sharding-algorithm-name: table-inline
# 表分片列名称
sharding-column: cid
以上这些配置较简单,最后我会附上工程,大家可以拿去借鉴,只修改yml配置即可,不用修改逻辑代码:
HINT
该方式我单独拎出来,是因为这个是能实现精准定位表来更新数据的方式,但是数据库的分片逻辑需要配置。
配置
spring:
application:
name: sharding-jdbc
shardingsphere:
datasource:
# 数据源别名
db1:
# 数据库连接信息
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/tss_db?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
password: 1234
type: com.zaxxer.hikari.HikariDataSource
username: root
# 数据源别名列表
names: db1
# props:
# sql-show: true
rules:
sharding:
# 分片算法定义
sharding-algorithms:
# 分片算法定义名称
table-inline:
type: HINT_INLINE
props:
algorithm-expression: tss_table_$->{value}
# 分片算法定义名称
table-tenant-inline:
type: HINT_INLINE
props:
algorithm-expression: tss_tenant_table_$->{value}
tables:
tss_table:
# 配置表节点
actual-data-nodes: db1.tss_table_$->{1..31}
table-strategy:
hint:
# 表分片策略名称,在上面的 sharding-algorithms 内容中配置
sharding-algorithm-name: table-inline
tss_tenant_table:
# 配置表节点
actual-data-nodes: db1.tss_tenant_table_$->{1..31}
table-strategy:
hint:
# 表分片策略名称,在上面的 sharding-algorithms 内容中配置
sharding-algorithm-name: table-tenant-inline
最后我附上的工程中也有相关配置。
逻辑代码
@SpringBootTest
class TssTests {
@Resource
private TssTableMapper tssTableMapper;
/**
* tss数据库batch insert语句测试
*/
@Test
public void testTssBatchInsert() {
HintManager hintManager = HintManager.getInstance();
// 指定插到的表名及配置里写明的后缀
hintManager.addTableShardingValue("tss_table", 7);
List<TssTable> tssTableList = tssTableMapper.selectList(null);
hintManager.addTableShardingValue("tss_table", 6);
for(TssTable item : tssTableList){
tssTableMapper.insert(item.setId(null));
}
hintManager.close();
}
}
自定义分库分表(精准定位+范围查询)
这个是最灵活的一种方式,既需要配置也需要修改逻辑代码,我以上面提供的5.2.1版本的依赖方式实现:
配置
# tss水平分库分表精准分片
spring:
application:
name: sharding-jdbc
shardingsphere:
datasource:
# 数据源别名
db0:
# 数据库连接信息
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/tss_db?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
password: 1234
type: com.zaxxer.hikari.HikariDataSource
username: root
# 数据源别名
db1:
# 数据库连接信息
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/tss_db_2?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&maxReconnects=666&failOverReadOnly=false&initialTimeout=10&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
password: 1234
type: com.zaxxer.hikari.HikariDataSource
username: root
# 数据源别名列表
names: db0,db1
rules:
sharding:
# 算法配置
sharding-algorithms:
# 数据库分片算法
my-db-strategy:
# 算法类型:自定义算法
type: CLASS_BASED
props:
# 算法策略:标准算法
strategy: standard
# 自定义算法类路径
algorithmClassName: com.shardingjdbc.config.upgrade.MyDbShardingAlgorithm
# 表分片算法
my-tss-table-strategy:
# 算法类型:自定义算法
type: CLASS_BASED
props:
# 算法策略:标准算法
strategy: standard
# 自定义算法类路径
algorithmClassName: com.shardingjdbc.config.upgrade.TssTableShardingAlgorithm
# 表分片算法
my-tss-tenant-table-strategy:
# 算法类型:自定义算法
type: CLASS_BASED
props:
# 算法策略:标准算法
strategy: standard
# 自定义算法类路径
algorithmClassName: com.shardingjdbc.config.upgrade.TssTenantTableShardingAlgorithm
tables:
tss_table:
# 配置表节点
actual-data-nodes: db$->{0..1}.tss_table_$->{1..31}
database-strategy:
standard:
sharding-algorithm-name: my-db-strategy
sharding-column: tenant_id
table-strategy:
standard:
sharding-column: opt_date
sharding-algorithm-name: my-tss-table-strategy
tss_tenant_table:
# 配置表节点
actual-data-nodes: db$->{0..1}.tss_tenant_table_$->{1..31}
database-strategy:
standard:
sharding-algorithm-name: my-db-strategy
sharding-column: tenant_id
table-strategy:
standard:
sharding-column: opt_date
sharding-algorithm-name: my-tss-tenant-table-strategy
配置中一定要注意别忘了rules
关键字。
代码
精准定位数据库
在插入数据的时候,这个类【有PreciseShardingValue传参的doSharding方法】可以根据配置中的数据库策略传来的字段值,然后进行逻辑操作之后【返回数据库名】直接定位插入的数据库。
package com.shardingjdbc.config.upgrade;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
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.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
@Slf4j
public class MyDbShardingAlgorithm implements StandardShardingAlgorithm<String> {
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {
// 获取分片键的值
if(collection.isEmpty()){
log.error("------- shardingsphere log database list is empty !!!");
return "";
}
String value = preciseShardingValue.getValue();
String logicTableName = preciseShardingValue.getLogicTableName();
log.info("------- shardingsphere log database list:{}, value: {}, logicTableName: {}",collection,value,logicTableName);
// 计算hash
String dataSourceIndex = String.valueOf(Math.abs(value.hashCode()) % collection.size());
log.info("------- shardingsphere log dataSourceIndex: {}",dataSourceIndex);
// 排序,怕之后根据后缀定表会出现问题,2后缀有2,12,22,排序之后endWith筛选后选第一个就没有问题了
List<String> sortedDbName = collection.stream().sorted().collect(Collectors.toList());
// 查询对应数据库名
String result = sortedDbName.stream().filter(item -> item.endsWith(dataSourceIndex)).findFirst().orElse(null);
if(StringUtils.isBlank(result)){
result = sortedDbName.get(0);
}
return result;
}
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<String> rangeShardingValue) {
return null;
}
@Override
public Properties getProps() {
return null;
}
@Override
public void init(Properties properties) {
}
}
精准定位+范围查询表
- 可以根据自己的逻辑确定要插入数据的表名
- 可以根据查询的范围来指定筛选的表范围,避免全库全表查询,仅查询部分表即可,提高查询效率。
package com.shardingjdbc.config.upgrade;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.common.collect.Range;
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.util.*;
@Slf4j
public class TssTenantTableShardingAlgorithm implements StandardShardingAlgorithm<Date> {
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Date> preciseShardingValue) {
log.info("------- shardingsphere log table name get in TssTenantTableShardingAlgorithm collection = {} , preciseShardingValue = {}",collection, preciseShardingValue);
//可以获取三个值,也就是course逻辑表名,columnName id,value 获取的值
String logicTableName = preciseShardingValue.getLogicTableName();
Date value = preciseShardingValue.getValue();
Calendar cal = Calendar.getInstance();
cal.setTime(value);
String key = logicTableName + "_" + cal.get(Calendar.DAY_OF_MONTH);
log.info("------- shardingsphere log table name = {}",key);
if (StringUtils.isNotBlank(key)) {
return key;
}
return null;
}
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Date> rangeShardingValue) {
log.info("------- shardingsphere log table name get in TssTenantTableShardingAlgorithm collection = {} , rangeShardingValue = {}",collection, rangeShardingValue);
// 获取分片键值,时间类型的值(LocalDateTime等)会自动转为java.sql.Timestamp,可以直接用java.util.Date接收
Range<Date> valueRange = rangeShardingValue.getValueRange();
// 获取范围小值
Date lowerEndpoint = valueRange.lowerEndpoint();
// 获取范围大值
Date upperEndpoint = valueRange.upperEndpoint();
Calendar startTime = Calendar.getInstance();
Calendar endTime = Calendar.getInstance();
startTime.setTime(lowerEndpoint);
endTime.setTime(upperEndpoint);
// 逻辑表名
String logicTableName = rangeShardingValue.getLogicTableName();
Set<String> tables = new HashSet<>();
// 比较两个时间
while (startTime.compareTo(endTime) <= 0) {
// 添加到集合
tables.add(buildTable(logicTableName, startTime));
// 往后加一个月
startTime.add(Calendar.DAY_OF_MONTH,1);
}
if (tables.isEmpty()) {
log.error("------- shardingsphere log table name is empty");
}
log.info("------- shardingsphere log table name list = {}", tables);
return tables;
}
private String buildTable(String logicTableName, Calendar dateTime) {
return logicTableName + "_" + dateTime.get(Calendar.DAY_OF_MONTH);
}
@Override
public Properties getProps() {
return null;
}
@Override
public void init(Properties properties) {
}
}
代码仓库地址
https://gitee.com/hepai123/shardingjdbc-demo.git
[develop-tss-upgrade]
分支是上面说的【自定义分库分表(精准定位+范围查询)】
源码,除此之外,其他的源码全在 [develop-tss]
分支