Sharding-jdbc项目构建和基本本功能


引入依赖

springboot 整合 Sharding-jdbc,第一步依赖引入。

<!-- 指定spring-boot版本号 -->
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.3.12.RELEASE</version>
  <relativePath/>
</parent>
<!-- 指定依赖包的版本 -->
<properties>
  <mysql.version>8.0.30</mysql.version>
  <mybatis.version>2.2.2</mybatis.version>
  <druid.version>1.2.11</druid.version>
  <lombok.version>1.18.24</lombok.version>
  <sharding-jdbc-version>4.1.1</sharding-jdbc-version>
</properties>
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
      <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>${druid.version}</version>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql.version}</version>
  </dependency>
  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>${mybatis.version}</version>
  </dependency>
  <!-- sharding-jdbc依赖 -->
  <dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>${sharding-jdbc-version}</version>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

四种分片算法

在 Sharding-jdbc 中提供了四种分片算法,分别是精准分片算法、范围分片算法、复合分片算法和强制路由分片算法。

  • standard:精确分片算法,用于单个字段作为分片键,SQL 中有=in等条件的分片,需要在标准分片策略下使用,算法策略实现PreciseShardingAlgorithm接口。
  • complex:复合分片算法,用于多个字段作为分片键的分片操作,同时获取多个分片键的值,根据多个字段处理业务逻辑。算法策略实现ComplexKeysShardingAlgorithm接口。
  • Hint:强制路由分片算法,其他分片的算法都是根据分片键来实现路由到不同的数据库、表,但是在一些特殊的场景,需要强制将某个业务 SQL 路由到指定的数据库、表,就需要手动的干预,使用的就是 Hint 分片算法。算法策略实现HintShardingAlgorithm接口。
  • 范围分片算法:用于单个字段作为分片键,SQL 中有between and><>=<=,需要在标准策略下使用,算法策略实现RangeShardingAlgorithm接口。

四种分片算法接口中都有一个doSharding方法,方法中提供两个参数分别是:availableTargetNamesshardingValue。代码如下:(以ComplexKeysShardingAlgorithm为例)

/**
 * Type parameters: <T> – class type of sharding value
 * 这里指定的是数据类型,比如分片是用的主键id,id使用snowflake算法生成,那么这里的值类型就是Long
 */
public interface ComplexKeysShardingAlgorithm<T extends Comparable<?>> extends ShardingAlgorithm {
    /**
     * Sharding.
     *
     * @param availableTargetNames available data sources or tables's names
     * 表示的是数据库或者是数据表的列表,如果使用在数据库分片策略中,就是数据库列表,如果使用在数据表分片策略中,就是数据表列表
     * 如:["ds0","ds1","ds2"]
     * @param shardingValue sharding value
     * 获取对应分片键和值,ComplexKeysShardingValue中提供了对应的API,根据不同分片算法有所不同
     *
     * @return sharding results for data sources or tables's names
     */
    Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<T> shardingValue);
}
/**
 * 封装列名和对应的值
 */
@RequiredArgsConstructor
@Getter
@ToString
public final class ComplexKeysShardingValue<T extends Comparable<?>> implements ShardingValue {

    private final String logicTableName;

    /**
     * Map集合中,键表示分片策略中的列名,value是对应的值(针对值是精确的)
     */
    private final Map<String, Collection<T>> columnNameAndShardingValuesMap;

    /**
     * Map集合中,键表示分片策略中的列名,value是对应的值(针对值是范围的)
     */
    private final Map<String, Range<T>> columnNameAndRangeValuesMap;
}

主键生成策略

在单库的时候,主键可以采用的方式很多,如 uuid、自增长、snowflake 等等,但是在分库分表的情况下,要考虑的内容比较多,比如 ID 重复问题,如何根据 ID 将数据分配到对应的数据库、表等。

sharding-jdbc 提供了两种主键生成策略,分别是 UUID 和 snowflake,UUID 对数据库索引不友好,绝大部分情况都是使用 snowflake,在 sharding-jdbc 中默认使用的也是 snowflake。

UUID 和 snowflake 在 sharding-jdbc 中都有对应的内置策略类,分别是UUIDShardingKeyGeneratorSnowflakeShardingKeyGenerator。可以通过在application.yml配置文件中指定对应。

配置文件的写法

spring:
  application:
    name: shardingjdbc
  shardingsphere:
    datasource: # 指定数据源
      names: ds0,ds1,ds2 # 指定数据源的别名,可以和真实的数据库名不同
      ds0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/database_1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/database_2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
      ds2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/database_3?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
    props:
      sql.show: true # 表示在日志中打印执行的sql,方便测试分片是否成功
    sharding: # 指定分片策略
      tables:
        t_user:
          key-generator:
            column: id
            type: snowflake # 指定主键生成的策略
            props:
              worker:
                id: 1 # snowflake需要指定当前服务器的节点编号,防止主键重复,比如后端部署3台机器,保证每台编号不同,取值随意
          actualDataNodes: ds$->{0..2}.t_user # 这里的数据使用的是别名,对应定义数据源时指定的
          database-strategy:
            standard:
              sharding-column: id # 指定分片键
              # 指定精准分片算法策略
              precise-algorithm-class-name: top.iweek.sharding.DatasourcePreciseAlgorithm
              # 指定范围分片算法策略
              range-algorithm-class-name: top.iweek.sharding.DatasourceRangeAlgorithm
          table-strategy:
            standard:
              sharding-column: id
              precise-algorithm-class-name: top.iweek.sharding.DataTablePreciseAlgorithm
              range-algorithm-class-name: top.iweek.sharding.DataTableRangeAlgorithm

注意内容:

  1. 当主键使用 sharding-jdbc 生成策略的时候,在写insertSQL 的时候,不要指定 id,否则会报错,示例如下:
--- 常规写法
insert into t_user(id,username,age) values (#{id},#{username},#{age});
--- 使用sharding-jdbc以后的写法
insert into t_user(username,age) values (#{username},#{age});

如果不这样写,一般会报一个错误,错误信息如下:

java.lang.IllegalArgumentException: Sharding value must implements Comparable.
  1. snowflake 算法是在每个服务中的,ID 中包含时间戳、机器码、序列号和高位,高位值固定式 0,序列号是同一毫秒内产生的不同 ID,最大值是 4095。如果此时两台机器的机器码没有特殊指定,当出现时间戳、序列号相同,就会出现重复 ID 问题。因此在配置的之后一定要指定机器码。也就是配置文件中的worker.id。具体 snowflake 实现原理移步:分布式唯一 Id(雪花算法):原理+对比+方案

策略算法的实现

本案例根据精准分片来做的,需要实现PreciseShardingAlgorithm接口。实现逻辑如下:

  • 数据库分片规则:通过策略算法确定路由到的数据库名称
@Component
@Slf4j
public class DatasourcePreciseAlgorithm implements PreciseShardingAlgorithm<Long> {
  @Override
  public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
    // 获取分片列的名字,根据配置文件确定这里是id
    String columnName = preciseShardingValue.getColumnName();
    // 获取分片列对应的值
    long columnValue =preciseShardingValue.getValue();
    log.info("columnName=>{},columnValue=>{}", columnName, columnValue);
    // 计算确定具体的分片
    int dbIndex = (int)(columnValue % 3L);
    // 返回分片数据库的名称
    // collection集合内容["ds0","ds1","ds2"],对应配置文件中指定的数据库别名列表
    return (String)collection.toArray()[dbIndex];
  }
}
  • 数据表分片规则:通过策略算法确定路由到的数据表名称
@Component
@Slf4j
public class DataTablePreciseAlgorithm implements PreciseShardingAlgorithm<Long> {
  @Override
  public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
    // 每个数据库中只有一个t_user表,这里不用分片,直接使用集合中第一个即可
    // collection集合是表的集合["t_user"]
    // 如果分为多个表的时候,示例集合["t_user_0","t_user_1","t_user_2"]
    // 通过分片算法计算出结果,确定使用具体表名并返回
    return (String)collection.toArray()[0];
  }
}

sharding-jdbc 执行过程原理

先看图:

  • SQL 解析:主要是词法和语法的解析。
  • SQL 路由:根据分片规则配置以及解析上下文中的分片条件,将 SQL 定位至真正的数据源。路由分类如下:

  • SQL 改写:将 SQL 改写为在真实数据库中可以正确执行的语句。SQL 改写分为正确性改写和优化改写。

  • SQL 执行:通过多线程执行器异步执行。

  • 结果归并:将多个执行结果集归并以便于通过统一的 JDBC 接口输出。结果归并包括流式归并、内存归并和使用装饰者模式的追加归并这几种方式。


文章作者: 程序猿洞晓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 程序猿洞晓 !
评论
  目录