ShardingSphere初探
文章目录
ShardingSphere
ShardingSphere 是一个分布式数据库中间件生态系统,旨在解决数据库的分布式架构问题。它提供了数据分片、读写分离、弹性缩容、分布式事务、数据加密等功能,帮助企业构建高性能、可扩展的分布式数据库解决方案。 由 Apache 软件基金会孵化和维护,是一个开源项目。
主要组件
- Sharding-JDBC:
- 是一个轻量级的 Java 框架,嵌入在应用程序中,提供 JDBC 连接池和 SQL 解析、改写、执行等功能。它通过在应用层实现分片逻辑,支持各种关系数据库。
- Sharding-Proxy:
- 是一个独立的代理层,支持任何兼容 MySQL、PostgreSQL 协议的客户端。它拦截 SQL 请求,执行分片、读写分离等操作,返回聚合结果给客户端。
- Sharding-Sidecar(又称 Sharding-Sphere on Kubernetes):
- 是针对 Kubernetes 环境设计的微服务解决方案,通过 Sidecar 模式实现数据库的分片和读写分离,适用于云原生应用。
核心功能
- 数据分片(Sharding):
- 通过水平分表和分库的方式,将数据分散存储到多个数据库实例中,提升系统的扩展性和性能。
- 读写分离:
- 通过主从复制,实现读写请求的分离,减轻主数据库的压力,提高读操作的性能。
- 分布式事务:
- 支持 XA、BASE 等分布式事务协议,确保在分布式环境中的数据一致性。
- 弹性缩容:
- 支持在线添加或删除数据库节点,动态调整分片规则,实现系统的弹性扩展和收缩。
- 数据加密:
- 提供数据加密功能,确保存储数据的安全性。
优点
- 开源和社区支持:ShardingSphere 是 Apache 基金会的顶级项目,拥有活跃的社区和丰富的文档资源。
- 灵活性:支持多种数据库,兼容性好,易于与现有系统集成。
- 高性能:通过分片和读写分离,提高了数据库的并发处理能力。
- 易扩展:弹性缩容功能使系统能应对动态变化的业务需求。
ShardingSphere 适用于需要高可用性、高性能和高扩展性的分布式数据库应用场景,如互联网、电商、金融等行业。
Sharding-JDBC和Sharding-proxy的对比
特性 | Sharding-JDBC | Sharding-Proxy |
---|---|---|
架构类型 | 嵌入式 | 代理层 |
部署位置 | 应用程序内部 | 独立部署于服务器之间 |
适用语言 | Java | 支持任何兼容 MySQL、PostgreSQL 协议的客户端 |
性能 | 高(无网络开销) | 取决于网络延迟和代理层性能 |
透明性 | 对应用程序透明,需少量配置 | 对客户端透明,只需配置代理地址 |
支持的数据库 | MySQL, PostgreSQL, Oracle, SQL Server, 等 | MySQL, PostgreSQL |
读写分离 | 支持 | 支持 |
数据分片 | 支持 | 支持 |
分布式事务 | 支持 | 支持 |
兼容性 | 与 ORM 框架(如 Hibernate、MyBatis)无缝集成 | 兼容所有符合协议的客户端 |
部署和运维 | 需要在每个应用程序中部署和配置 | 集中管理,简化了客户端的部署 |
扩展性 | 取决于应用程序的扩展性 | 容易通过增加代理节点来扩展 |
复杂性 | 应用程序内增加复杂性,需要修改和配置应用程序 | 中心化配置,减少应用程序的复杂性 |
适用场景 | 小型到中型系统,单体应用 | 大型系统,微服务架构,需支持多种语言的环境 |
-
Sharding-JDBC 更适合需要高性能和对数据库操作有精细控制的小型到中型 Java 应用程序
-
Sharding-Proxy 更适合需要语言无关的解决方案的大型系统或微服务架构。
Sharding-jdbc水平分表演示
水平分表
- 概念:将一个大表的数据按照某种规则(如根据用户ID)分成多个小表,每个小表包含一部分数据。
- 优点:减小了单表的大小,提升了查询和写入性能。
- 缺点:需要处理跨表查询和数据的均衡分布问题。
假如我们现在有个逻辑表course,详细的sql如下
CREATE TABLE `course` (
`cid` bigint NOT NULL AUTO_INCREMENT,
`cname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`user_id` bigint DEFAULT NULL,
`cstatus` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=1012831258863996929 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;RSET=utf8mb4 COLLATE=utf8mb4_general_ci;
逻辑表和物理表划分
此时由于业务需要,需要将course表水平拆分。具体拆分规则如下,将course表拆分成course_1和course_2。此时course_1和course_2为物理表,实际储存数据的表。
相应的表sql如下,只是表名不同,字段完全一样,创建好这两张表
CREATE TABLE `course_1` (
`cid` bigint NOT NULL AUTO_INCREMENT,
`cname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`user_id` bigint DEFAULT NULL,
`cstatus` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=1012831258863996929 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;RSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `course_2` (
`cid` bigint NOT NULL AUTO_INCREMENT,
`cname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`user_id` bigint DEFAULT NULL,
`cstatus` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=1012831258863996929 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;RSET=utf8mb4 COLLATE=utf8mb4_general_ci;
分片键和分片算法的选取
由于cid的唯一性,可以采用雪花算法生成cid,并可利用哈希算法对cid取模操作,均匀分配到每个数据分片上面
对应的分片算法为 shard = (cid % 2 + 1)
Sharding-jdbc 基于SpringBoot演示
- 创建sharding-demo-1的SpringBoot项目,并引入以下依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 创建entity表Course
public class Course {
private Long cid;
private String cname;
@TableField("user_id")
private Long userid;
private String cstatus;
...getter setter toString
}
- 创建CourseMapper
@Mapper
public interface CourseMapper extends BaseMapper<Course> {
}
- 配置sharding-jdbc相关参数
spring:
shardingsphere:
datasource:
#配置第一个数据源m1
names: m1
#配置第一个数据源m1的详细信息
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.56.102:3306/coursedb?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
#配置分片表的详细信息
sharding:
tables:
#配置逻辑表course的主键生成规则和详细的分片策略
course:
#配置逻辑表和物理表之间的关系,此时将course分成course_1和course_2
actual-data-nodes: m1.course_$->{1..2}
# 配置主键生成规则
key-generator:
column: cid
type: SNOWFLAKE
props:
worker:
id: 1
table-strategy:
inline:
# 配置分片键
sharding-column: cid
#配置分片算法
algorithm-expression: course_$->{cid % 2 + 1}
props:
sql:
#开启sp sql日志
show: true
- 创建ShardingTest测试
@SpringBootTest
@RunWith(SpringRunner.class)
public class ShardingTest {
@Resource
private CourseMapper courseMapper;
@Test
public void addCourse(){
for (int i = 0; i < 10; i++) {
Course course = new Course();
course.setCname("c++");
course.setUserid(180L);
course.setCstatus("2");
courseMapper.insert(course);
}
}
@Test
public void findCourse(){
QueryWrapper<Course> objectQueryWrapper = new QueryWrapper<>();
List<Course> courses = courseMapper.selectList(objectQueryWrapper);
System.out.println(courses.size());
}
}
运行addCourse(),观察数据库中是否插入数据
course_1内容如下:
cid | cname | user_id | cstatus |
---|---|---|---|
1012880134358700032 | c++ | 180 | 2 |
1012880134933319680 | c++ | 180 | 2 |
1012880135017205760 | c++ | 180 | 2 |
1012880135096897536 | c++ | 180 | 2 |
1012880135159812096 | c++ | 180 | 2 |
course_2内容如下:
cid | cname | user_id | cstatus |
---|---|---|---|
1012880134887182337 | c++ | 180 | 2 |
1012880134979457025 | c++ | 180 | 2 |
1012880135046565889 | c++ | 180 | 2 |
1012880135130451969 | c++ | 180 | 2 |
1012880135180783617 | c++ | 180 | 2 |
查看console台日志
2024-06-27 00:24:02.517 INFO 24260 --- [ main] ShardingSphere-SQL : SQLStatement: InsertStatementContext(super=CommonSQLStatementContext(sqlStatement=org.apache.shardingsphere.sql.parser.sql.statement.dml.InsertStatement@e91af20, tablesContext=org.apache.shardingsphere.sql.parser.binder.segment.table.TablesContext@6221b13b), tablesContext=org.apache.shardingsphere.sql.parser.binder.segment.table.TablesContext@6221b13b, columnNames=[cname, user_id, cstatus], insertValueContexts=[InsertValueContext(parametersCount=3, valueExpressions=[ParameterMarkerExpressionSegment(startIndex=59, stopIndex=59, parameterMarkerIndex=0), ParameterMarkerExpressionSegment(startIndex=62, stopIndex=62, parameterMarkerIndex=1), ParameterMarkerExpressionSegment(startIndex=65, stopIndex=65, parameterMarkerIndex=2), DerivedParameterMarkerExpressionSegment(super=ParameterMarkerExpressionSegment(startIndex=0, stopIndex=0, parameterMarkerIndex=3))], parameters=[c++, 180, 2])], generatedKeyContext=Optional[GeneratedKeyContext(columnName=cid, generated=true, generatedValues=[1012880134358700032])])
2024-06-27 00:24:02.518 INFO 24260 --- [ main] ShardingSphere-SQL : Actual SQL: m1 ::: INSERT INTO course_1 ( cname,
user_id,
cstatus , cid) VALUES (?, ?, ?, ?) ::: [c++, 180, 2, 1012880134358700032]
2024-06-27 00:24:02.560 INFO 24260 --- [ main] ShardingSphere-SQL : Logic SQL: INSERT INTO course ( cname,
user_id,
cstatus ) VALUES ( ?,
?,
? )
其中包含雪花算法生成的id值
Actual SQL表示实际执行的sql语句
m1 ::: INSERT INTO course_1 ( cname,
user_id,
cstatus , cid) VALUES (?, ?, ?, ?) ::: [c++, 180, 2, 1012880134358700032]
Logic SQL:表示逻辑sql语句
INSERT INTO course ( cname,
user_id,
cstatus ) VALUES ( ?,
?,
? )
ShardingSphere就是将分片键经过分片策略,将逻辑sql转化成真实sql执行
Sharding-jdbc水平分库分表演示
需要将第二个库配置进yaml,并在第二个库中新建course_1和course_2表
spring:
shardingsphere:
datasource:
#配置两个数据源m1,m2
names: m1,m2
#配置第一个数据源m1的详细信息
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.109.94.124:3306/coursedb?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
#配置第二个数据源m2的详细信息
m2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.109.188.99:3306/coursedb?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
#配置分片表的详细信息
sharding:
tables:
#配置逻辑表course的主键生成规则和详细的分片策略
course:
#配置逻辑表和物理表之间的关系,此时将m分成m1和m2将course分成course_1和course_2
actual-data-nodes: m$->{1..2}.course_$->{1..2}
# 配置主键生成规则
key-generator:
column: cid
type: SNOWFLAKE
props:
worker:
id: 1
#配置库的策略
database-strategy:
inline:
# 配置分片键
sharding-column: cid
#配置库的分片算法
algorithm-expression: m$->{cid % 2 + 1}
table-strategy:
inline:
# 配置分片键
sharding-column: cid
#配置分片算法
algorithm-expression: course_$->{((cid+1)%4).intdiv(2)}
props:
sql:
#开启sp sql日志
show: true
查询演示
查询所有的course表信息
public void findCourse(){
QueryWrapper<Course> objectQueryWrapper = new QueryWrapper<>();
List<Course> courses = courseMapper.selectList(objectQueryWrapper);
System.out.println(courses.size());
}
2024-06-27 16:48:18.195 INFO 20524 --- [ main] ShardingSphere-SQL : Logic SQL: SELECT cid,cname,user_id,cstatus FROM course
2024-06-27 16:48:18.195 INFO 20524 --- [ main] ShardingSphere-SQL : SQLStatement: SelectStatementContext(super=CommonSQLStatementContext(sqlStatement=org.apache.shardingsphere.sql.parser.sql.statement.dml.SelectStatement@1d3c112a, tablesContext=org.apache.shardingsphere.sql.parser.binder.segment.table.TablesContext@2a140ce5), tablesContext=org.apache.shardingsphere.sql.parser.binder.segment.table.TablesContext@2a140ce5, projectionsContext=ProjectionsContext(startIndex=8, stopIndex=32, distinctRow=false, projections=[ColumnProjection(owner=null, name=cid, alias=Optional.empty), ColumnProjection(owner=null, name=cname, alias=Optional.empty), ColumnProjection(owner=null, name=user_id, alias=Optional.empty), ColumnProjection(owner=null, name=cstatus, alias=Optional.empty)]), groupByContext=org.apache.shardingsphere.sql.parser.binder.segment.select.groupby.GroupByContext@1f71194d, orderByContext=org.apache.shardingsphere.sql.parser.binder.segment.select.orderby.OrderByContext@db99785, paginationContext=org.apache.shardingsphere.sql.parser.binder.segment.select.pagination.PaginationContext@70716259, containsSubquery=false)
2024-06-27 16:48:18.195 INFO 20524 --- [ main] ShardingSphere-SQL : Actual SQL: m1 ::: SELECT cid,cname,user_id,cstatus FROM course_1
2024-06-27 16:48:18.196 INFO 20524 --- [ main] ShardingSphere-SQL : Actual SQL: m1 ::: SELECT cid,cname,user_id,cstatus FROM course_2
2024-06-27 16:48:18.196 INFO 20524 --- [ main] ShardingSphere-SQL : Actual SQL: m2 ::: SELECT cid,cname,user_id,cstatus FROM course_1
2024-06-27 16:48:18.196 INFO 20524 --- [ main] ShardingSphere-SQL : Actual SQL: m2 ::: SELECT cid,cname,user_id,cstatus FROM course_2
根据实际执行sql看出,其实是将m1,m2的course_1和course_2都去执行一遍SELECT cid,cname,user_id,cstatus FROM course_$
,最后再聚合,将数据返回。
范围查询
根据cid的范围查询数据,如1L
到 1806251962652463105L
public void findRangeCourse(){
QueryWrapper<Course> objectQueryWrapper = new QueryWrapper<>();
objectQueryWrapper.between("cid", 1L, 1806251962652463105L);
List<Course> courses = courseMapper.selectList(objectQueryWrapper);
System.out.println(courses.size());
}
此时由于是inline的关系,会抛异常
Cause: java.lang.IllegalStateException: Inline strategy cannot support this type sharding:RangeRouteValue(columnName=cid, tableName=course, valueRange=[1‥1806251962652463105])
分片策略是inline
无法支持范围查询。
标准策略的定制
Sharding-jdbc支持分库分表的标准策略模式,可以根据自己需求定制策略。
分为精准策略
和range策略
.
- 精准策略需要实现
PreciseShardingAlgorithm
, - range策略需要实现
RangeShardingAlgorithm
下面分别定义库和表的策略,其中库的策略为
public class CourseDataSourcePrecisAlgorithm implements PreciseShardingAlgorithm<Long> {
private static final AtomicInteger COUNTER = new AtomicInteger(0);
/**
* 从availableTargetNames中根据分片键preciseShardingValue的值,选择一个数据节点名称。
*
* @param availableTargetNames 参数包含了所有可用的数据节点名称,比如数据源或数据表的名称。
* @param preciseShardingValue 参数包含了分片键的值和逻辑表名称。
* @return
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> preciseShardingValue) {
// 获取分片键的值
//选取数据源m1,m2
int andIncrement = COUNTER.getAndIncrement();
if(andIncrement % 2 == 0){
return "m1";
}else{
return "m2";
}
}
}
range并没有策略,此时返回所有的库,则ShardingSphere在执行范围查询的时候,会遍历所有的库。
public class CourseDataSourceRangeAlgorithm implements RangeShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> rangeShardingValue){
return availableTargetNames;
}
}
表的策略为
public class CourseDataSourcePrecisAlgorithm implements PreciseShardingAlgorithm<Long> {
/**
* 从availableTargetNames中根据分片键preciseShardingValue的值,选择一个数据节点名称。
* 选取数据源m1,m2
* 因为有4张表,所以利用cid对4取模,得到一个余数,余数是如果是0和1,则选择m1,如果是2和3,则选择m2
* @param availableTargetNames 参数包含了所有可用的数据节点名称,比如数据源或数据表的名称。
* @param preciseShardingValue 参数包含了分片键的值和逻辑表名称。
* @return
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> preciseShardingValue) {
// 获取分片键的值
Long cid = preciseShardingValue.getValue();
long mod = cid % 4;
if(mod == 0 || mod == 1){
return "m1";
}else if(mod == 2 || mod == 3){
return "m2";
}else{
throw new UnsupportedOperationException();
}
}
}
public class CourseTablePrecisAlgorithm implements PreciseShardingAlgorithm<Long> {
/**
* 因为有4张表,所以利用cid对4取模,得到一个余数,
* 余数如果是0和1,则选择m1,如果是0,则选择m1->course_1,如果是1,则选择m1->course_2
* 余数如果是2和3,则选择m2,,如果是2,则选择m2->course_1,如果是3,则选择m2->course_2
* 这里由于雪花算法在低并发下,由于同一毫秒只分配一个id则最后12位都是0,所以不存在2^0,故而雪花生成的id是2,4的倍数,取模偏移0区
* 数据量少时或者tps低时尽量避免取模2,4,如分片键
* @param collection
* @param preciseShardingValue
* @return
*/
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
//怎么根据cid来判断路由到哪个表,cid这里既需要经过算法成为1,也要成为2,还不能跟dataSource路由冲突
long mod = preciseShardingValue.getValue() % 4;
//根据mod能知道该条数据应该路由到哪个库中
if(mod == 0 || mod == 2){
String key = preciseShardingValue.getLogicTableName() + "_" + 1;
if(collection.contains(key)){
return key;
}
throw new UnsupportedOperationException();
}else if(mod == 1 || mod == 3){
String key = preciseShardingValue.getLogicTableName() + "_" + 2;
if(collection.contains(key)) {
return key;
}
throw new UnsupportedOperationException();
}
throw new UnsupportedOperationException();
}
}
yaml中需要更改为如下配置
spring:
shardingsphere:
datasource:
#配置第一个数据源m1
names: m1,m2
#配置第一个数据源m1的详细信息
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.109.94.124:3306/coursedb?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: SXD6VRjq
m2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.109.188.99:3306/coursedb?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: xDLnuQtK
#配置分片表的详细信息
sharding:
tables:
#配置逻辑表course的主键生成规则和详细的分片策略
course:
#配置逻辑表和物理表之间的关系,此时将course分成course_1和course_2
actual-data-nodes: m$->{1..2}.course_$->{1..2}
# 配置主键生成规则
key-generator:
column: cid
type: SNOWFLAKE
props:
worker:
id: 1
#配置库的策略
database-strategy:
standard:
# 配置分片策略
precise-algorithm-class-name: cn.axj.sharding.ShardingAlgorithmConfig.CourseDataSourcePrecisAlgorithm
range-algorithm-class-name: cn.axj.sharding.ShardingAlgorithmConfig.CourseDataSourceRangeAlgorithm
# 配置分片键
sharding-column: cid
table-strategy:
standard:
range-algorithm-class-name: cn.axj.sharding.ShardingAlgorithmConfig.CourseTableRangeAlgorithm
precise-algorithm-class-name: cn.axj.sharding.ShardingAlgorithmConfig.CourseTablePrecisAlgorithm
# 配置分片键
sharding-column: cid
props:
sql:
#开启sp sql日志
show: true
执行范围查询
public void findRangeCourse(){
QueryWrapper<Course> objectQueryWrapper = new QueryWrapper<>();
objectQueryWrapper.between("cid", 1L, 1806251962652463105L);
List<Course> courses = courseMapper.selectList(objectQueryWrapper);
System.out.println(courses.size());
}
2024-06-27 17:27:00.263 INFO 11432 --- [ main] ShardingSphere-SQL : Actual SQL: m1 ::: SELECT cid,cname,user_id,cstatus FROM course_1
WHERE (cid BETWEEN ? AND ?) ::: [1, 1806251962652463105]
2024-06-27 17:27:00.263 INFO 11432 --- [ main] ShardingSphere-SQL : Actual SQL: m1 ::: SELECT cid,cname,user_id,cstatus FROM course_2
WHERE (cid BETWEEN ? AND ?) ::: [1, 1806251962652463105]
2024-06-27 17:27:00.263 INFO 11432 --- [ main] ShardingSphere-SQL : Actual SQL: m2 ::: SELECT cid,cname,user_id,cstatus FROM course_1
WHERE (cid BETWEEN ? AND ?) ::: [1, 1806251962652463105]
2024-06-27 17:27:00.263 INFO 11432 --- [ main] ShardingSphere-SQL : Actual SQL: m2 ::: SELECT cid,cname,user_id,cstatus FROM course_2
可以看到实际执行sql如上。每个库每个表都查询,最后聚合。
标准策略针对in、=、between三种范围查询
复合策略的定制
多个字段组合的查询,则需要用到复合策略complex
spring:
shardingsphere:
datasource:
#配置第一个数据源m1
names: m1,m2
#配置第一个数据源m1的详细信息
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.109.94.124:3306/coursedb?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: SXD6VRjq
m2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.109.188.99:3306/coursedb?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: xDLnuQtK
#配置分片表的详细信息
sharding:
tables:
#配置逻辑表course的主键生成规则和详细的分片策略
course:
#配置逻辑表和物理表之间的关系,此时将course分成course_1和course_2
actual-data-nodes: m$->{1..2}.course_$->{1..2}
# 配置主键生成规则
key-generator:
column: cid
type: SNOWFLAKE
props:
worker:
id: 1
#配置库的策略
database-strategy:
complex:
#配置分片键
sharding-columns: cid,user_id
# 配置分片策略
algorithm-class-name: cn.axj.sharding.ShardingAlgorithmConfig.CourseDatabaseComplexAlgorithm
table-strategy:
complex:
sharding-columns: cid,user_id
algorithm-class-name: cn.axj.sharding.ShardingAlgorithmConfig.CourseTableComplexAlgorithm
props:
sql:
#开启sp sql日志
show: true
public class CourseDatabaseComplexAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<Long> complexKeysShardingValue) {
Long userId = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("cid").iterator().next();
Long cid = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("cid").iterator().next();
// 自定义分片逻辑,示例中简单处理
// 假设根据 user_id 和 order_id 进行取模分片
String dataSourceName = "m" + ((userId + cid) % availableTargetNames.size() + 1);
for (String targetName : availableTargetNames) {
if (targetName.endsWith(dataSourceName)) {
return Collections.singleton(targetName); // 返回匹配的表名
}
}
throw new IllegalArgumentException("No precise sharding available for " + complexKeysShardingValue);
}
}
public class CourseTableComplexAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<Long> complexKeysShardingValue) {
Long userId = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("cid").iterator().next();
Long cid = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("cid").iterator().next();
// 自定义分片逻辑,示例中简单处理
// 假设根据 user_id 和 order_id 进行取模分片
String tableName = complexKeysShardingValue.getLogicTableName() + "_" + ((userId + cid) % availableTargetNames.size() + 1);
for (String targetName : availableTargetNames) {
if (targetName.endsWith(tableName)) {
return Collections.singleton(targetName); // 返回匹配的表名
}
}
throw new IllegalArgumentException("No precise sharding available for " + complexKeysShardingValue);
}
}
hint策略的定制
hint策略,按照与sql无关的值进行分片,所以无需分片键这些,只需要一个分片算法
分片的值通过HintManger手动设置, 如下示例。
AtomicInteger atomicInteger = new AtomicInteger(0);
HintManager instance = HintManager.getInstance();
instance.setDatabaseShardingValue(atomicInteger.getAndIncrement() % 2 + 1);
instance.addTableShardingValue("course", atomicInteger.getAndIncrement() % 2 + 1);
public class CourseDatabaseHintAlgorithm implements HintShardingAlgorithm<String> {
@Override
public Collection<String> doSharding(Collection<String> collection, HintShardingValue<String> hintShardingValue) {
String key = "m" + hintShardingValue.getValues().toArray()[0];
if(collection.contains(key)){
return Collections.singletonList(key);
}
throw new UnsupportedOperationException();
}
}
public class CourseTableHintAlgorithm implements HintShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, HintShardingValue<Long> shardingValue){
String logicTableName = shardingValue.getLogicTableName();
//该值需要在查询或者DDL的时候通过HintManager设置
Collection<Long> values = shardingValue.getValues();
String key = logicTableName + "_" + shardingValue.getValues().toArray()[0];
if(availableTargetNames.contains(key)){
return Collections.singletonList(key);
}
throw new UnsupportedOperationException();
}
}
spring:
shardingsphere:
datasource:
#配置第一个数据源m1
names: m1,m2
#配置第一个数据源m1的详细信息
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.109.94.124:3306/coursedb?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: SXD6VRjq
m2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.109.188.99:3306/coursedb?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: xDLnuQtK
#配置分片表的详细信息
sharding:
tables:
#配置逻辑表course的主键生成规则和详细的分片策略
course:
#配置逻辑表和物理表之间的关系,此时将course分成course_1和course_2
actual-data-nodes: m$->{1..2}.course_$->{1..2}
# 配置主键生成规则
key-generator:
column: cid
type: SNOWFLAKE
props:
worker:
id: 1
#配置库的策略
database-strategy:
hint:
algorithm-class-name: cn.axj.sharding.ShardingAlgorithmConfig.CourseDatabaseHintAlgorithm
table-strategy:
hint:
algorithm-class-name: cn.axj.sharding.ShardingAlgorithmConfig.CourseTableHintAlgorithm
props:
sql:
#开启sp sql日志
show: true
比如在新增数据的时候,需要利用HintManage将分片值传进去
public void addHintCourse(){
AtomicInteger atomicInteger = new AtomicInteger(0);
HintManager instance = HintManager.getInstance();
for (int i = 0; i < 200; i++) {
instance.setDatabaseShardingValue(atomicInteger.getAndIncrement() % 2 + 1);
instance.addTableShardingValue("course", atomicInteger.getAndIncrement() % 2 + 1);
Course course = new Course();
course.setCname("c++");
course.setUserid(180L);
course.setCstatus("2");
courseMapper.insert(course);
}
}