ShardingSphere初探

ShardingSphere初探

ShardingSphere

ShardingSphere 是一个分布式数据库中间件生态系统,旨在解决数据库的分布式架构问题。它提供了数据分片、读写分离、弹性缩容、分布式事务、数据加密等功能,帮助企业构建高性能、可扩展的分布式数据库解决方案。 由 Apache 软件基金会孵化和维护,是一个开源项目。

主要组件
  1. Sharding-JDBC
    • 是一个轻量级的 Java 框架,嵌入在应用程序中,提供 JDBC 连接池和 SQL 解析、改写、执行等功能。它通过在应用层实现分片逻辑,支持各种关系数据库。
  2. Sharding-Proxy
    • 是一个独立的代理层,支持任何兼容 MySQL、PostgreSQL 协议的客户端。它拦截 SQL 请求,执行分片、读写分离等操作,返回聚合结果给客户端。
  3. Sharding-Sidecar(又称 Sharding-Sphere on Kubernetes)
    • 是针对 Kubernetes 环境设计的微服务解决方案,通过 Sidecar 模式实现数据库的分片和读写分离,适用于云原生应用。
核心功能
  1. 数据分片(Sharding)
    • 通过水平分表和分库的方式,将数据分散存储到多个数据库实例中,提升系统的扩展性和性能。
  2. 读写分离
    • 通过主从复制,实现读写请求的分离,减轻主数据库的压力,提高读操作的性能。
  3. 分布式事务
    • 支持 XA、BASE 等分布式事务协议,确保在分布式环境中的数据一致性。
  4. 弹性缩容
    • 支持在线添加或删除数据库节点,动态调整分片规则,实现系统的弹性扩展和收缩。
  5. 数据加密
    • 提供数据加密功能,确保存储数据的安全性。
优点
  • 开源和社区支持:ShardingSphere 是 Apache 基金会的顶级项目,拥有活跃的社区和丰富的文档资源。
  • 灵活性:支持多种数据库,兼容性好,易于与现有系统集成。
  • 高性能:通过分片和读写分离,提高了数据库的并发处理能力。
  • 易扩展:弹性缩容功能使系统能应对动态变化的业务需求。

ShardingSphere 适用于需要高可用性、高性能和高扩展性的分布式数据库应用场景,如互联网、电商、金融等行业。

Sharding-JDBC和Sharding-proxy的对比
特性Sharding-JDBCSharding-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_1course_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演示
  1. 创建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>
  1. 创建entity表Course
public class Course {

    private Long cid;

    private String cname;

    @TableField("user_id")
    private Long userid;

    private String cstatus;
    ...getter setter toString
    
}
  1. 创建CourseMapper
@Mapper
public interface CourseMapper extends BaseMapper<Course> {

}
  1. 配置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
  1. 创建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内容如下:

cidcnameuser_idcstatus
1012880134358700032c++1802
1012880134933319680c++1802
1012880135017205760c++1802
1012880135096897536c++1802
1012880135159812096c++1802

course_2内容如下:

cidcnameuser_idcstatus
1012880134887182337c++1802
1012880134979457025c++1802
1012880135046565889c++1802
1012880135130451969c++1802
1012880135180783617c++1802

查看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的范围查询数据,如1L1806251962652463105L

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);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值