ShardingSphere中的ShardingJDBC常见分片算法的实现

ShardingJDBC

Git地址

快速入门

现在我存在两个数据库,并且各自都有两个数据表。我现在先新增一批用户,分别插入两个数据库的多张数据表中

在这里插入图片描述



基本环境搭建

实体类

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
// 注意,我这里给的表名是sys_user 这是一个逻辑表名  而上图中是不存在这个数据表的,
@TableName("sys_user")
@ApiModel(value="User对象", description="用户表")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 实体类中的属性最好不用使用id,因为mybatisplus会自动识别id属性,并在新增时使用雪花算法为id属性赋值。
     * 而不会使用ShardingSphere的雪花算法生成,如果一定要使用id属性名,那么就要加上@TableId(value = "uid", type = IdType.AUTO)
     *
     * 也不要在分片键上使用 @TableId(value = "uid", type = IdType.ASSIGN_ID) 这样是使用的mybatisplus的雪花算法生成的key,
     * 也不会使用ShardingSphere的雪花算法生成
     */
    @ApiModelProperty(value = "uid")
//    @TableId(value = "uid", type = IdType.ASSIGN_ID)
    private Long uid;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "姓名")
    private String name;

    @ApiModelProperty(value = "描述")
    private String description;

    @ApiModelProperty(value = "状态(1:正常 0:停用)")
    private Integer status;

    @ApiModelProperty(value = "创建时间")
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    @ApiModelProperty(value = "最后修改时间")
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}



Mapper接口

@Mapper
public interface UserMapper extends BaseMapper<User> {
}



maven依赖

<!-- shardingJDBC核心依赖 -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.2.1</version>
    <exclusions>
        <exclusion>
            <artifactId>snakeyaml</artifactId>
            <groupId>org.yaml</groupId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 坑爹的版本冲突 -->
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.33</version>
</dependency>

<!-- SpringBoot依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>snakeyaml</artifactId>
            <groupId>org.yaml</groupId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 数据源连接池 -->
<!--注意不要用这个依赖,他会创建数据源,跟上面ShardingJDBC的SpringBoot集成依赖有冲突 -->
<!--        <dependency>-->
<!--            <groupId>com.alibaba</groupId>-->
<!--            <artifactId>druid-spring-boot-starter</artifactId>-->
<!--            <version>1.1.20</version>-->
<!--        </dependency>-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.20</version>
</dependency>



上方 snakeyaml 依赖冲突会报下面的错误

Description:

An attempt was made to call a method that does not exist. The attempt was made from the following location:

    org.apache.shardingsphere.infra.util.yaml.constructor.ShardingSphereYamlConstructor$1.<init>(ShardingSphereYamlConstructor.java:44)

The following method did not exist:

    org.apache.shardingsphere.infra.util.yaml.constructor.ShardingSphereYamlConstructor$1.setCodePointLimit(I)V

The method's class, org.apache.shardingsphere.infra.util.yaml.constructor.ShardingSphereYamlConstructor$1, is available from the following locations:

    jar:file:/D:/softwareDev/maven/myMaven/repository/org/apache/shardingsphere/shardingsphere-infra-util/5.2.1/shardingsphere-infra-util-5.2.1.jar!/org/apache/shardingsphere/infra/util/yaml/constructor/ShardingSphereYamlConstructor$1.class

The class hierarchy was loaded from the following locations:

    null: file:/D:/softwareDev/maven/myMaven/repository/org/apache/shardingsphere/shardingsphere-infra-util/5.2.1/shardingsphere-infra-util-5.2.1.jar
    org.yaml.snakeyaml.LoaderOptions: file:/D:/softwareDev/maven/myMaven/repository/org/yaml/snakeyaml/1.26/snakeyaml-1.26.jar


Action:

Correct the classpath of your application so that it contains a single, compatible version of org.apache.shardingsphere.infra.util.yaml.constructor.ShardingSphereYamlConstructor$1

在这里插入图片描述



yml配置文件的内容

server:
  port: 8084


spring:
  shardingsphere:
    props:
      # 打印shardingjdbc的日志  shardingsphere5之前的版本配置项是 spring.shardingsphere.props.sql.show,而这里是sql-show
      sql-show: true
    # 数据源配置
    datasource:
      # 虚拟库的名字,并指定对应的真实库
      names: hsdb0,hsdb1
      hsdb0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/sharding_sphere1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 1234
      hsdb1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/sharding_sphere2?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 1234

    # 分布式序列算法配置
    rules:
      sharding:
        key-generators:
          # 雪花算法,生成Long类型主键。
          # alg_snowflake为我们程序员自定义的字符串名字,可以更改
          alg_snowflake:
            type: SNOWFLAKE  # 建议使用COSID_SNOWFLAKE雪花算法,避免数据分布不均匀
            props:
              worker:
                id: 1
        sharding-algorithms:
          # 指定分库策略 按2取模方式  sys_user_db_alg是我们程序员自定义的字符串名字,可以更改
          sys_user_db_alg:
            type: MOD
            props:
              sharding-count: 2
          # 指定分表策略  INLINE:按单一分片键分表
          sys_user_tbl_alg:
            type: INLINE
            props:
              # 通过uid对2取模 + 1 最终就会是使用sys_user1  sys_user2两张数据表
              algorithm-expression: sys_user$->{uid%2+1}


        # 指定分布式主键生成策略
        tables:
          # sys_user为虚拟表名,可以更改,但需要和实体类中的表名对应上
          sys_user:
            # 指定虚拟表的分片键,以及分片键的生成策略
            key-generate-strategy:
              # 指定分片建为 uid 这个是和数据表中的字段对应的
              column: uid
              # 指定分片键字段的生成策略, alg_snowflake 也就是我们上面自定义的雪花算法
              key-generator-name: alg_snowflake
            # 配置实际分片节点 $->{1..2}表达式的功能就是[1,2]  hsdb0.sys_user1  hsdb0.sys_user2  hsdb1.sys_user1  hsdb1.sys_user2
            actual-data-nodes: hsdb$->{0..1}.sys_user$->{1..2}
            # 配置分库策略,按uid字段取模
            database-strategy:
              standard:
                sharding-column: uid
                sharding-algorithm-name: sys_user_db_alg
            # 指定分表策略  standard-按单一分片键进行精确或范围分片
            table-strategy:
              standard:
                sharding-column: uid
                sharding-algorithm-name: sys_user_tbl_alg



测试类,进行新增10条记录

@SpringBootTest
@RunWith(SpringRunner.class)
public class TestShardingJDBC {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testAdd(){
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setUsername("husahng" + i);
            user.setPassword("123456");
            userMapper.insert(user);
        }
    }
}



日志信息

在这里插入图片描述



插入结果,我们能发现,目前新增的数据被分到了两张数据表中,数据的分布并不平衡。这是因为我们在yml配置文件中指定的分库策略和分表策略导致的

我们的分库策略是根据uid%2 最终决定使用哪一个数据库,分表策略是uid%2+1 。

所以就导致了如果uid为偶数就会存放在hsdb0.sys_user1数据表;如果uid是奇数就会存放在hsdb1.sys_user2数据表




在这里插入图片描述

在这里插入图片描述



修改雪花算法和分表策略

我们接下来就修改一下分表策略

而将sys_user_tbl_alg的计算表达式从sys_user$->{uid%2+1}改成 sys_user$->{((uid+1)%4).intdiv(2)+1} 后,理论上,如果uid是连续递增的,就可以将数据均匀分到四个表里。但是snowflake雪花算法生成的ID并不是连续的,所以有时候还是无法分到四个表。

如下图所示,已经修改了分表策略,但还是只分到hsdb0.sys_user1数据表和hsdb1.sys_user2数据表



在这里插入图片描述
20240803120302397.png&pos_id=img-SabPy9cD-1722676476932)

在这里插入图片描述



所以我们还需要修改一下雪花算法,不使用snowflake雪花算法。关于这一块,后续文档会详细介绍,这里就简单使用mybatisplus的主键通过雪花算法生成

@TableId(value = "uid", type = IdType.ASSIGN_ID)
private Long uid;



或者是使用COSID_SNOWFLAKE雪花算法

在这里插入图片描述



此时数据就平衡的插入到多张数据表中了

在这里插入图片描述



核心概念

  • 虚拟库

    ShardingSphere的核心就是提供一个具备分库分表功能的虚拟库,他是一个ShardingSphereDatasource实例。应用程序只需要像操作单数据源一样访问这个ShardingSphereDatasource即可。

    上方案例中,hsdb1 hsdb2就是虚拟库

  • 真实库

    实际保存数据的数据库。真实库被包含在ShardingSphereDataSource实例中,由ShardingSphere来决定未来使用哪一个真实库

    上方案例中,sharding_sphere1和sharding_sphere2就是真实库

  • 逻辑表

    应用程序直接操作的逻辑表。

    上方案例中,sys_user就是逻辑表

  • 真实表

    真实保存数据的表。逻辑表和真实表的表名不需要一致,但需要有一致的表结构。应用程序可以维护一个真实表与逻辑表的对应关系。

    上方案例中,sys_ser1 sys_user2就是真实表

  • 分布式主键生成算法

    给逻辑表生成唯一的主键。

    由于逻辑表的数据是分布在多个真实表当中的,所以单表的索引就无法保证逻辑表的ID唯一性。ShardingSphere集成了几种常见的基于单机生成的分布式主键生成器。比如SNOWFLAKE,COSID_SNOWFLAKE雪花算法可以生成单调递增的long类型的数字主键,还有UUID,NANOID可以生成字符串类型的主键。当然,ShardingSphere也支持应用自行扩展主键生成算法。比如基于Redis,Zookeeper等第三方服务,自行生成主键。

  • 分片策略

    表示逻辑表要如何分配到真实库和真实表当中,分为分库策略和分表策略两个部分。

    分片策略由分片键和分片算法组成。分片键是进行数据水平拆分的关键字段。如果没有分片键,ShardingSphere将只能进行全路由,SQL执行的性能会非常差。分片算法则表示根据分片键如何寻找对应的真实库和真实表。



分片算法

简单INLINE分片算法

可以对分片键使用 = 或者是 in 进行精确查找。uid通过下面的分片表达式计算出要查询的真实库和真实表。

如果in操作涉及到了多个分片 并且 涉及到了多个数据表,也会进行多分片多数据表的查询

默认情况下不能使用范围查询

我们现在yml的配置的分表策略是

spring:
  shardingsphere:
    rules:
      sharding:
        sharding-algorithms:
          sys_user_tbl_alg:
            # 分片策略是 INLINE
            type: INLINE
            props:
              # 通过(uid+1) %4 /2 +1
              algorithm-expression: sys_user$->{((uid+1)%4).intdiv(2)+1}
// 使用 = 对分片进行精确查询
@Test
public void testInline(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("uid", 1819587788580864004L);
    User user = userMapper.selectOne(queryWrapper);
    System.out.println(user);
}



日志信息,仅仅查询到一个库的一个表进行查询

Logic SQL: SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user WHERE (uid = ?)

Actual SQL: hsdb0 ::: SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user1 
WHERE (uid = ?) ::: [1819587788580864004]



// in操作 涉及到的都在是一个分片库中
@Test
public void testInline(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.in("uid", 1819587788580864004L,1819587788513755138L);
    List<User> users = userMapper.selectList(queryWrapper);
    System.out.println(users);
}

日志信息,会查询hsdb0分片库中的sys_user1 和 sys_user2 数据表,并通过union关键字进行组合查询

Logic SQL: SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user WHERE (uid IN (?,?))

Actual SQL: hsdb0 ::: SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user1 
WHERE (uid IN (?,?)) 
UNION 
ALL SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user2 
WHERE (uid IN (?,?)) ::: [1819587788580864004, 1819587788513755138, 1819587788580864004, 1819587788513755138]



// in操作 涉及到两个分片中的sys_user1数据表
@Test
public void testInline(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.in("uid", 1819587788580864004L,1819587788580864003L);
    List<User> users = userMapper.selectList(queryWrapper);
    System.out.println(users);
}



此时我更改一个查询的uid,让它涉及到两个分片中的sys_user1数据表,此时从打印的日志就可以发现这里只查询了两个分片的sys_user1数据表

Logic SQL: SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user WHERE (uid IN (?,?))

Actual SQL: hsdb0 ::: SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user1 
 WHERE (uid IN (?,?)) ::: [1819587788580864004, 1819587788580864003]
Actual SQL: hsdb1 ::: SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user1 
 WHERE (uid IN (?,?)) ::: [1819587788580864004, 1819587788580864003]



// in操作 涉及到两个分片中的两张数据表,主要就是sys_user$->{((uid+1)%4).intdiv(2)+1}这个分片表达式不能很好的进行区分了
@Test
public void testInline(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.in("uid", 1819587788580864004L,1819587784852127745L);
    List<User> users = userMapper.selectList(queryWrapper);
    System.out.println(users);
}

此时我更改一个查询的uid,让它设计到两个分片的两个数据表,此时从打印的日志就可以发现这里进行了两个分片与两个数据表的查询

Logic SQL: SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user WHERE (uid IN (?,?))

ctual SQL: hsdb0 ::: SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user1 
WHERE (uid IN (?,?)) UNION ALL SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user2 
WHERE (uid IN (?,?)) ::: [1819587788580864004, 1819587784852127745, 1819587788580864004, 1819587784852127745]
 
 Actual SQL: hsdb1 ::: SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user1 
 WHERE (uid IN (?,?)) UNION ALL SELECT  uid,username,password,name,description,status,create_time,update_time  FROM sys_user2 
 WHERE (uid IN (?,?)) ::: [1819587788580864004, 1819587784852127745, 1819587788580864004, 1819587784852127745]



STANDARD标准分片算法

使用简单INLINE分片算法,它默认情况下不支持范围查询。

我们可以通过添加下面这个参数让它支持范围查询,只不过此时的查询是通过全分片路由查询

# 允许在inline策略中使用范围查询。  sys_user_tbl_alg 是自定义名称
spring.shardingsphere.rules.sharding.sharding-algorithms.sys_user_tbl_alg.props.allow-range-query-with-inline-sharding=true



@Test
public void testInline(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.between("uid", 1819587788513755138L, 1819587788580864004L);
    List<User> users = userMapper.selectList(queryWrapper);
    System.out.println(users);
}

报错信息




在这里插入图片描述



# 添加下面的配置 允许在inline策略中使用范围查询。 sys_user_tbl_alg 是自定义名称
spring.shardingsphere.rules.sharding.sharding-algorithms.sys_user_tbl_alg.props.allow-range-query-with-inline-sharding=true

此时的查询就会通过全分片路由查询



在Class_based自定义分片算法中,如果使用标准分片算法则实现下面的接口StandardShardingAlgorithm

public interface StandardShardingAlgorithm<T extends Comparable<?>> extends ShardingAlgorithm {
    String doSharding(Collection<String> var1, PreciseShardingValue<T> var2);

    // 如果是范围查询,那么我们就可以通过使用RangeShardingValue 对象来进行自定义范围查询的分片算法
    Collection<String> doSharding(Collection<String> var1, RangeShardingValue<T> var2);
}



COMPLEX_INLINE复杂分片算法

complex_inline复杂分片策略,它支持多个分片键。

建议所有的分片键是同一个类型



修改yml配置

# 分表策略-COMPLEX:按多个分片键组合分表  sys_user_tbl_alg 为我们自定义字符串 可以修改
spring.shardingsphere.rules.sharding.sharding-algorithms.sys_user_tbl_alg.type=COMPLEX_INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.sys_user_tbl_alg.props.algorithm-expression=sys_user$->{(uid+status+1)%2+1}

#给sys_user表指定分表策略 sys_user是虚拟表名   按uid, status多个分片键进行组合分片  sys_user_tbl_alg要和上面的对应上
spring.shardingsphere.rules.sharding.tables.sys_user.table-strategy.complex.sharding-columns=uid, status
spring.shardingsphere.rules.sharding.tables.sys_user.table-strategy.complex.sharding-algorithm-name=sys_user_tbl_alg

在这里插入图片描述



进行新增

@Test
public void testAdd(){
    for (int i = 0; i < 10; i++) {
        User user = new User();
        user.setUsername("husahng" + i);
        user.setPassword("123456");
        user.setName("name");
        user.setDescription("描述信息");
        // 给status也设置值
        user.setStatus(1);
        userMapper.insert(user);
    }
}



通过两个分片字段进行查询

@Test
public void testComplexInline(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.in("uid", 1026510115563372545L,1026510115626287104L);
    queryWrapper.eq("status", 0);
    List<User> users = userMapper.selectList(queryWrapper);
    System.out.println(users);
}



CLASS_BASED自定义分片算法

我们可以通过自定义分片算法,能够提前判断一些不合理的查询条件。就比如我当前status的值只有0和1,如果查询的时候status为3就根本没必要去查询数据库了。

对于complex_inline复杂的分片算法,用一个简单的分片表达式也有点不太够用了。

我们可以创建一个java类,实现ShardingSphere提供的ComplexKeysShardingAlgorithm接口 自己在java代码中写相应的分片策略

STANDARD-> StandardShardingAlgorithm

COMPLEX->ComplexKeysShardingAlgorithm 这里就拿复杂的分片策略举例

HINT -> HintShardingAlgorithm



package com.hs.sharding.algorithm;

import com.google.common.collect.Range;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingValue;
import org.apache.shardingsphere.sharding.exception.syntax.UnsupportedShardingOperationException;

import java.util.*;

/**
 * 实现自定义COMPLEX分片策略
 * 声明算法时,ComplexKeysShardingAlgorithm接口可传入一个泛型,这个泛型就是分片键的数据类型。
 * 这个泛型只要实现了Comparable接口即可。
 * 但是官方不建议声明一个明确的泛型出来,建议是在doSharding中再做类型转换。这样是为了防止分片键类型与算法声明的类型不符合。
 * <p>
 * 我此时uid就是long   status是int   我进行测试
 *
 * @auth roykingw
 */
public class MyComplexAlgorithm implements ComplexKeysShardingAlgorithm<Long> {

    private Properties props;

    @Override
    public Collection<String> doSharding(Collection<String> collection, ComplexKeysShardingValue<Long> complexKeysShardingValue) {
        // 假如此时SQL为 select * from sys_user where uid in (xxx,xxx,xxx) and status between {lowerEndpoint} and {upperEndpoint};
        Collection<Long> uidList = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("uid");
        Range<Long> status = complexKeysShardingValue.getColumnNameAndRangeValuesMap().get("status");

        // 拿到status的查询范围
        if (status != null) {
            Long lowerEndpoint = status.lowerEndpoint();
            Long upperEndpoint = status.upperEndpoint();
            if (lowerEndpoint > upperEndpoint) {
                // 如果下限 > 上限  抛出异常,终止去数据库查询的操作
                throw new UnsupportedShardingOperationException("empty record query", "sys_user");
            } else if (lowerEndpoint < 0 || upperEndpoint > 1) {
                // 如果查询范围明显不在status字段的区间中
                //抛出异常,终止去数据库查询的操作
                throw new UnsupportedShardingOperationException("error range query param", "sys_user");
            }
        }

        // 校验都通过后,就按照自定义的分片规则来进行分片,我这里就按照uid的奇偶来进行
        List<String> result = new ArrayList<>();
        // 获取逻辑表名 sys_user   而我们的真实表为sys_user1  sys_user2
        String logicTableName = complexKeysShardingValue.getLogicTableName();
        for (Long uid : uidList) {
            String resultTableName = logicTableName + ((uid + 1) % 4 /2 + 1);
            if (!result.contains(resultTableName)) {
                result.add(resultTableName);
            }

        }
        return result;
    }

    @Override
    public Properties getProps() {
        return props;
    }

    @Override
    public void init(Properties properties) {
        this.props = props;
    }
}



在核心的dosharding方法当中,就可以按照我们之前的规则进行判断。不满足规则,直接抛出UnsupportedShardingOperationException异常,就可以组织ShardingSphere把SQL分配到真实数据库中执行。

接下来,还是需要增加策略配置,让course表按照这个规则进行分片。

# 使用CLASS_BASED分片算法- 不用配置SPI扩展文件
spring.shardingsphere.rules.sharding.sharding-algorithms.sys_user_tbl_alg.type=CLASS_BASED
# 指定策略 STANDARD|COMPLEX|HINT
spring.shardingsphere.rules.sharding.sharding-algorithms.sys_user_tbl_alg.props.strategy=COMPLEX
# 指定算法实现类。这个类必须是指定的策略对应的算法接口的实现类。 
# STANDARD-> StandardShardingAlgorithm;  COMPLEX->ComplexKeysShardingAlgorithm;  HINT -> HintShardingAlgorithm
spring.shardingsphere.rules.sharding.sharding-algorithms.sys_user_tbl_alg.props.algorithmClassName=com.roy.shardingDemo.algorithm.MyComplexAlgorithm




在这里插入图片描述

此时,新增或查询就都会经过上方我们自定义的MyComplexAlgorithm.doSharding()方法来选择具体的分表策略了



HINT_INLINE强制分片算法

使用常见,查询虚拟字段的情况,HINT强制路由可以用一种与SQL无关的方式进行分库分表。



就比如我仅仅查询uid是奇数的情况,如果是单机就可以直接在Mapper接口中添加下面的sql即可

@Select("select * from sys_user where MOD(uid,2)=1")

我们使用ShardingSphere当然也能查询,只不过又是全分片路由查询。因为此时我们是根据uid的奇偶来进行分片的,所以自然是希望只去查询某一个真实表,这种基于虚拟列的查询语句,ShardingSphere不知道该去进行解析,那么就只能是全路由查询了。

实际上ShardingSphere无法正常解析的语句还有很多。基本上用上分库分表后,你的应用就应该要和各种多表关联查询、多层嵌套子查询、distinct查询等各种复杂查询分手了。



这个uid的奇偶关系并不能通过SQL语句正常体现出来,这时,就需要用上ShardingSphere提供的另外一种分片算法HINT强制路由。HINT强制路由可以用一种与SQL无关的方式进行分库分表。



注释掉之前给course表分配的分表算法,重新分配一个HINT_INLINE类型的分表算法

#给sys_user表指定分表策略 sys_user为虚拟表 可以修改  hint-与SQL无关的方式进行分片
spring.shardingsphere.rules.sharding.tables.sys_user.table-strategy.hint.sharding-algorithm-name=sys_user_tbl_alg
# 分表策略-HINT:用于SQL无关的方式分表,使用value关键字。  sys_user_tbl_alg 为自定义string
spring.shardingsphere.rules.sharding.sharding-algorithms.sys_user_tbl_alg.type=HINT_INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.sys_user_tbl_alg.props.algorithm-expression=sys_user$->{value}

在这里插入图片描述



然后,在应用进行查询时,使用HintManager给HINT策略指定value的值。

@Test
public void queryCourseByHint(){
    HintManager hintManager = HintManager.getInstance();
    // 强制查sys_user1表
    hintManager.addTableShardingValue("sys_user","1");

    try {
        List<User> users = userMapper.selectList(null);
        System.out.println(users);
    } finally {

        //线程安全,所有用完要注意关闭。
        hintManager.close();
        //hintManager关闭的主要作用是清除ThreadLocal,释放内存。HintManager实现了AutoCloseable接口,
        // 所以建议使用try-resource的方式,用完自动关闭。
        //try(HintManager hintManager = HintManager.getInstance()){ xxxx }
    }


}



注意事项

  • 实体类分片键属性名不要使用id,mybatisplus会自动识别id,并为它使用雪花算法生成值。如果想使用ShardingSphere的雪花算法生成值就需要注意这个问题

  • ShardingSphere的雪花算法不是自增长的,对它进行取模分片时可能出现数据分片不均匀情况,可以自定义雪花算法

  • 不要使用批量插入

    insert into course values (1,'java',1001,1),(2,'java',1001,1);
    

    但是这样的SQL语句如果交给ShardingSphere去执行,那么这个语句就会造成困惑。到底应该往course_1还是course_2里插入?对于这种没有办法正确执行的SQL语句,ShardingSphere就会抛出异常。Insert statement does not support sharding table routing to multiple data nodes.

  • 简单inline分片算法,默认支持单分片键的 = in 精确查询,默认情况下不支持范围查询。需要开启一个配置

    allow-range-query-with-inline-sharding=true

  • standard标准分片算法,支持精确查询和范围查询

  • complex_inline 复杂分片算法,支持多分片键。但是建议分片键的类型是统一的

  • class_based自定义分片算法,比较灵活,可以通过java代码来指定使用的分片逻辑。可以通过实现相应的接口来实现该功能

  • hint_inline强制分片算法,直接在代码中强制指定查询的分片逻辑

要自定义分片算法,可以按照以下步骤操作: 1. 实现ShardingAlgorithm接口,该接口有一个方法doSharding,用于根据分片键和分片策略计算出目标数据节点。 ```java public interface ShardingAlgorithm<T extends Comparable<?>> { /** * 计算分片结果. * * @param availableTargetNames 所有的可用数据节点名称集合, 一般情况下与数据源名称相同 * @param shardingValue 分片值 * @return 分片后指向的数据节点名称,即逻辑数据源名称 */ String doSharding(Collection<String> availableTargetNames, ShardingValue<T> shardingValue); } ``` 其ShardingValue是分片值对象,包含分片键的值和分片算法的类型。availableTargetNames是所有可用的数据节点名称集合,一般情况下与数据源名称相同。 2. 使用自定义分片算法 在使用shardingsphere-sharding-boot-starter时,可以在application.yml配置分片规则和自定义分片算法。假设要对user表按照年龄进行分片,并使用自定义分片算法,可以按照以下配置: ```yaml spring: shardingsphere: datasource: names: ds0, ds1 ds0: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 username: root password: root ds1: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/test1?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 username: root password: root sharding: tables: user: actual-data-nodes: ds$->{0..1}.user_$->{0..1} table-strategy: standard: sharding-column: age precise-algorithm-class-name: com.example.MyPreciseShardingAlgorithm range-algorithm-class-name: com.example.MyRangeShardingAlgorithm key-generator: column: id type: SNOWFLAKE ``` 其,MyPreciseShardingAlgorithm和MyRangeShardingAlgorithm分别是自定义的精确分片算法和范围分片算法,可以在代码实现。 需要注意的是,自定义分片算法必须实现ShardingAlgorithm接口,并在application.yml配置算法类的全限定名。在配置,可以通过sharding-column指定分片键,通过actual-data-nodes指定真实的数据节点。具体的配置可以根据实际情况进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值