Skip to content

Sharding-Sphere

官方文档:概览 :: ShardingSphere (apache.org)

Sharding-JDBC

项目构建

pom.xml

xml
<!--整合SpringBoot3.X版本-->

<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>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        <version>3.5.7</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
        <version>8.0.26</version>
    </dependency>


    <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.20</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc-core -->
    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>shardingsphere-jdbc-core</artifactId>
        <version>5.4.1</version>
    </dependency>
    
    <!--解决java.lang.NoSuchMethodError: org.yaml.snakeyaml.representer.Representer: method 'void <init>()' not found,5.4.1版本存在此问题-->
    <dependency>
        <groupId>org.glassfish.jaxb</groupId>
        <artifactId>jaxb-runtime</artifactId>
        <version>2.3.8</version>
    </dependency>

    <!--解决javax.xml.bind.JAXBException: Implementation of JAXB-API has not been found on module path or classpath.,5.4.1版本存在此问题-->
    <dependency>
        <groupId>org.yaml</groupId>
        <artifactId>snakeyaml</artifactId>
        <version>1.33</version>
    </dependency>
</dependencies>

application.properties

properties
# 指定配置文件的位置是:sharding.yaml
spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver
spring.datasource.url=jdbc:shardingsphere:classpath:sharding.yaml

java

java
// pojo类
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
}

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

//启动类
@SpringBootApplication
@MapperScan("com.fjut.shardingsphere.mapper")
public class ShardingSphereApplication {
	public static void main(String[] args) {
		SpringApplication.run(ShardingSphereApplication.class, args);
	}
}

inline表达式

properties
actual-data-nodes: ds$->{0..1}.shoping_0$->{0..1}

# 释义:
ds$->{0..1}则表示:ds0、ds1
# 也可以这样写:
ds$->{['0','1']}
# 也可以组合起来用:
ds$->{['0','1']}.shoping_0$->{0..1}
# 也可以写一些函数
user_$->{Math.abs(username.hashCode()) % 2}

读写分离

yml配置

yml
# 配置模式,单机模式
mode:
  type: Standalone
  repository:
    type: JDBC

# 数据源配置
dataSources:
  # 主数据库配置,名称自定义
  master1:
    # 数据源类名,这里使用的是阿里巴巴的Druid连接池
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,包括数据库地址、端口、数据库名以及连接参数
    url: jdbc:mysql://192.168.6.136:3306/user
    username: remote
    password: fujianz123
  # 从数据库1配置,名称自定义
  slave1:
    # 数据源类名,与主数据库相同
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名,与主数据库相同
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,与主数据库类似,但端口不同
    url: jdbc:mysql://192.168.6.135:3306/user
    username: remote
    password: fujianz123
   
rules:
  # 配置读写分离
  - !READWRITE_SPLITTING
    dataSources:
     # 名称自定义
      readwrite_ds:
        # 用于读数据源的名称
        writeDataSourceName: master1
        # 用于写数据源的名称
        readDataSourceNames:
          - slave1
        transactionalReadQueryStrategy: PRIMARY
        # 负载均衡策略
        loadBalancerName: random
    loadBalancers:
      #随机负载均衡,名称自定义
      random:
        type: RANDOM
      # 轮询负载均衡,名称自定义
      robin:
        type: ROUND_ROBIN
      #按照权重负载均衡 ,名称自定义
      weight:
        type: WEIGHT
        props:
        	slave1: 1
        	slave2: 2
  # 用于指定哪些单表需要被 ShardingSphere 管理,如果使用的表不被其包含,就会报错
  - !SINGLE
    tables:
      #- ds_0.t_single # 加载指定单表
      #- ds_1.* # 加载指定数据源中的全部单表
      - "*.*" # 加载全部单表


# 属性配置
props:
  # 是否显示执行的SQL语句
  sql-show: true

事务模型

为了保证主从库间的事务一致性,避免跨服务的分布式事务,ShardingSphere-JDBC的主从模型中,事务中的数据读写均用主库

  • 不添加@Transactional:insert对主库操作,select对从库操作
  • 添加@Transactional:则insert和select均对主库操作
  • 注意:在JUnit环境下的@Transactional注解,默认情况下就会对事务进行回滚(即使在没加注解@Rollback,也会对事务回滚)

分库分表

逻辑表:提供给应用程序操作的表名,程序可以像操作原本的单表一样,灵活的操作逻辑表。

真实表:在各个数据库节点上真实存在的物理表,但表名一般都会和逻辑表存在偏差。

数据节点:主要是用于定位具体真实表的库表名称,如DB1.tb_user1、DB2.tb_user2.....

  • 均匀分布:指一张表的数量在每个数据源中都是一致的。
  • 自定义分布:指一张表在每个数据源中,具体的数量由自己来定义,下图就是一种自定义分布。

垂直分片

专库专用。 按照业务将表进行归类,分布到不同的数据库中。一般而言,分布式架构的系统默认都有独享库的概念,所以不需要通过shardingsphere来实现。

yml
# 配置模式,单机模式
mode:
  type: Standalone
  repository:
    type: JDBC

# 数据源配置
dataSources:
  # 主数据库配置
  master1:
    # 数据源类名,这里使用的是阿里巴巴的Druid连接池
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,包括数据库地址、端口、数据库名以及连接参数
    url: jdbc:mysql://192.168.6.136:3306/user
    username: remote
    password: fujianz123
  # 从数据库1配置
  slave1:
    # 数据源类名,与主数据库相同
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名,与主数据库相同
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,与主数据库类似,但端口不同
    url: jdbc:mysql://192.168.6.135:3306/user
    username: remote
    password: fujianz123

rules:
  # 对于user表的查询,将会到master1节点下的user表,对于user_info表的查询,将回到slave1节点下的user_info表
  - !SHARDING
    tables: # 数据分片规则配
      # 逻辑表名称
      user: 
        actualDataNodes : master1.user # 由数据源名 + 表名组成(参考 Inline 语法规则)
      # 逻辑表名称
      user_info: 
        actualDataNodes: slave1.user_info # 由数据源名 + 表名组成(参考 Inline 语法规则)
  - !SINGLE
    tables:
      #- ds_0.t_single # 加载指定单表
      #- ds_1.* # 加载指定数据源中的全部单表
      - "*.*" # 加载全部单表


# 属性配置
props:
  # 是否显示执行的SQL语句
  sql-show: true

水平分片

  • 对于数据库:id%2,偶数id将会落入0库,奇数id将会落入1库
  • 对于数据表:Math.abs(username.hashCode()) % 2,值为偶数将会落入0库,奇数将会落入1库
yml
# 配置模式,单机模式
mode:
  type: Standalone
  repository:
    type: JDBC

# 数据源配置
dataSources:
  # 主数据库配置
  db_0:
    # 数据源类名,这里使用的是阿里巴巴的Druid连接池
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,包括数据库地址、端口、数据库名以及连接参数
    url: jdbc:mysql://192.168.6.138:3306/user
    username: remote
    password: fujianz123
  # 从数据库1配置
  db_1:
    # 数据源类名,与主数据库相同
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名,与主数据库相同
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,与主数据库类似,但端口不同
    url: jdbc:mysql://192.168.6.137:3306/user
    username: remote
    password: fujianz123

rules:
  - !SHARDING
    # 数据分片规则配置
    tables:
      # 逻辑表名称
      user: 
        actualDataNodes : db_$->{0..1}.user_$->{0..1} # 由数据源名 + 表名组成(参考 Inline 语法规则)
        # 用于指定主键
        keyGenerateStrategy: 
          # 自增列名称,缺省表示不使用自增主键生成器
          column: id 
          # 分布式序列算法名称
          keyGeneratorName: snowflake 
          # mybatisplus需要使用@TableId(type = IdType.AUTO)标注实体类
          
       # 数据库分片策略
        databaseStrategy : 
          standard:
            shardingColumn: id
            shardingAlgorithmName: database_sharding
       # 表分片策略
        tableStrategy: 
          standard:
            shardingColumn: username
            shardingAlgorithmName: table_sharding
            
	  #defaultDatabaseStrategy: # 默认数据库分片策略
      #defaultTableStrategy: # 默认表分片策略
      #defaultKeyGenerateStrategy: # 默认的分布式序列策略
      #defaultShardingColumn: # 默认分片列名称
      
	#主键生成算法
    keyGenerators:
      snowflake: # 分布式序列算法名称
        type: SNOWFLAKE

	# 路由分片算法
    shardingAlgorithms:
      database_sharding:
        type: INLINE
        props:
          algorithm-expression: db_$->{id % 2}
      table_sharding:
        type: INLINE
        props:
          algorithm-expression: user_$->{Math.abs(username.hashCode()) % 2}

  - !SINGLE
    tables:
      #- ds_0.t_single # 加载指定单表
      #- ds_1.* # 加载指定数据源中的全部单表
      - "*.*" # 加载全部单表


# 属性配置
props:
  # 是否显示执行的SQL语句
  sql-show: true

详细配置

逻辑表

相同结构的水平拆分数据库(表)的逻辑名称。 例:订单数据根据主键尾数拆分为 10 张表,分别是 t_order_0t_order_9,他们的逻辑表名为 t_order

真实表

在水平拆分的数据库中真实存在的物理表。 即 t_order_0t_order_9

绑定表

为了避免跨库关联查询,如一笔订单记录中,多笔订单详情记录被分发到不同的节点中存储,联表查询时需要访问多个节点。可以使用绑定表,通过分片键关联查询,关联的表必须使用相同的分片键和分配策略

  • 不使用绑定表,联表查询将会是关联库的笛卡尔积

  • 使用绑定表,根据分片键联查,只会关联绑定的表

yml
# 数据源配置
dataSources:
  # 主数据库配置
  db_0:
    # 数据源类名,这里使用的是阿里巴巴的Druid连接池
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,包括数据库地址、端口、数据库名以及连接参数
    url: jdbc:mysql://192.168.6.140:3306/user
    username: remote
    password: fujianz123
  # 从数据库1配置
  db_1:
    # 数据源类名,与主数据库相同
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名,与主数据库相同
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,与主数据库类似,但端口不同
    url: jdbc:mysql://192.168.6.139:3306/user
    username: remote
    password: fujianz123

rules:
  - !SHARDING
    tables:
      # user表
      user:
        actualDataNodes: db_$->{0..1}.user_$->{0..1}
        keyGenerateStrategy:
          column: id
          keyGeneratorName: snowflake
        databaseStrategy:
          standard:
            shardingColumn: id
            shardingAlgorithmName: user_database_sharding
        tableStrategy:
          standard:
            shardingColumn: id
            shardingAlgorithmName: user_table_sharding
      # user_info表
      user_info:
        actualDataNodes: db_$->{0..1}.user_info_$->{0..1}
        keyGenerateStrategy:
          column: user_id
          keyGeneratorName: snowflake
        databaseStrategy:
          standard:
            shardingColumn: user_id
            shardingAlgorithmName: userInfo_database_sharding
        tableStrategy:
          standard:
            shardingColumn: user_id
            shardingAlgorithmName: userInfo_table_sharding

	# 绑定表user和user_info
    bindingTables:
      - user,user_info

    keyGenerators:
      snowflake:
        type: SNOWFLAKE

    shardingAlgorithms:
      user_database_sharding:
        type: INLINE
        props:
          algorithm-expression: db_$->{id % 2}
      userInfo_database_sharding:
        type: INLINE
        props:
          algorithm-expression: db_$->{user_id % 2}
      user_table_sharding:
        type: INLINE
        props:
          algorithm-expression: user_$->{Math.abs(id.hashCode()) % 2}
      userInfo_table_sharding:
        type: INLINE
        props:
          algorithm-expression: user_info_$->{Math.abs(user_id.hashCode()) % 2}

  - !SINGLE
    tables:
      #- ds_0.t_single # 加载指定单表
      #- ds_1.* # 加载指定数据源中的全部单表
      - "*.*" # 加载全部单表


# 属性配置
props:
  # 是否显示执行的SQL语句
  sql-show: true
广播表

所有的分片数据源中都存在的表,单表查询时只会到一个数据库查询,关联查询时仅会在自己数据库内查询,插入会向所有数据库的广播表插入,表结构及其数据在每个数据库中均完全一致。 适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。

yml
# 配置模式,单机模式
mode:
  type: Standalone
  repository:
    type: JDBC

# 数据源配置
dataSources:
  # 主数据库配置
  db_0:
    # 数据源类名,这里使用的是阿里巴巴的Druid连接池
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,包括数据库地址、端口、数据库名以及连接参数
    url: jdbc:mysql://192.168.6.141:3306/user
    username: remote
    password: fujianz123
  # 从数据库1配置
  db_1:
    # 数据源类名,与主数据库相同
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名,与主数据库相同
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,与主数据库类似,但端口不同
    url: jdbc:mysql://192.168.6.142:3306/user
    username: remote
    password: fujianz123


  - !SINGLE
    tables:
      #- ds_0.t_single # 加载指定单表
      #- ds_1.* # 加载指定数据源中的全部单表
      - "*.*" # 加载全部单表
# 配置广播表  
  - !BROADCAST
    tables:
      - dict

# 属性配置
props:
  # 是否显示执行的SQL语句
  sql-show: true
单表

并非所有的表都需要做分库分表操作,所以当一张表的数据无需分片到多个数据源中时,就可将其配置为单表,这样所有的读写操作最终都会落入这一张单表中处理。

yml
# 数据源配置
dataSources:
  # 从数据库1配置
  mysql:
    # 数据源类名,与主数据库相同
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名,与主数据库相同
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,与主数据库类似,但端口不同
    url: jdbc:mysql://localhost:3306/pratice
    username: root
    password: fujianz123


rules:
  - !SINGLE
    tables:
      #- ds_0.t_single # 加载指定单表
      #- ds_1.* # 加载指定数据源中的全部单表
      - "*.*" # 加载全部单表
    # 未在规则中配置的单表,查询都会走mysql数据源,也可以在上方指明
    defaultDataSource: mysql

自定义分片算法

实现StandardShardingAlgorithm接口

  • Collection:存放的是所有可路由的数据节点,如果配置的是库级别的分片策略,这里存放的是所有库的名称;如果配置的是表级别的分片策略,这里存放的是所有表的名称
  • PreciseShardingValue
    • preciseShardingValue.getLogicTableName(); :获取路由的逻辑表名
    • preciseShardingValue.getColumnName():获取路由键
    • preciseShardingValue.getValue():获取路由键的值
java
public class ShardingConfig implements StandardShardingAlgorithm {

    // 实现精确查询的方法(in、=查询会调用方法)
    @Override
    public String doSharding(Collection collection, PreciseShardingValue preciseShardingValue) {
        
    }

    // 实现范围查询的方法(BETWEEN AND、>、<、>=、<=会调用的方法)
    @Override
    public Collection<String> doSharding(Collection collection, RangeShardingValue rangeShardingValue) {
        
    }
}

//Complex自定义分片,适用于多路由键的场景,一张表需要通过多个核心字段查询时,可以配置多个路由键,此时就需要自己实现分片路由的算法。
public class Shardingsphere implements ComplexKeysShardingAlgorithm {
    @Override
    public Collection<String> doSharding(Collection collection, ComplexKeysShardingValue complexKeysShardingValue) 		{
        return null;
    }
}

//Hint:当一张表经常需要执行一些较为复杂的SQL语句时,这种SQL语句Sharding-Sphere无法自动解析,就可以自己编写Hint策略的实现类,强制指定这些SQL落入到哪些节点中处理。
public class Shardingsphere implements HintShardingAlgorithm {
    @Override
    public Collection<String> doSharding(Collection collection, HintShardingValue hintShardingValue) {
        return null;
    }
}
根据地址路由到不同库
java
public class ShardingConfig implements StandardShardingAlgorithm {

    // 实现精确查询的方法(in、=查询会调用方法)
    @Override
    public String doSharding(Collection collection, PreciseShardingValue preciseShardingValue) {
        // 获取本次SQL语句中具体的路由键值
        String address = (String)preciseShardingValue.getValue();
        String targetTable = "";

        //福建则路由到db_0,浙江则路由到db_1
        if (address.equals("福建"))
            targetTable+= "db_0";
        if (address.equals("浙江"))
            targetTable+= "db_1";

        // 判断计算出的目标表是否在Logic_DB中存在
        if (collection.contains(targetTable))
            // 如果配置的数据节点中有这张表,则直接返回目标表名
            return targetTable;

        // 不存在则抛出相应的异常信息
        throw new UnsupportedOperationException(targetTable +
                "表在逻辑库中不存在,请检查你的SQL语句或数据节点配置...");
    }

    // 实现范围查询的方法(BETWEEN AND、>、<、>=、<=会调用的方法)
    @Override
    public Collection<String> doSharding(Collection collection, RangeShardingValue rangeShardingValue) {
        return collection;
    }
}
yml
# 数据源配置
dataSources:
  # 主数据库配置
  db_0:
    # 数据源类名,这里使用的是阿里巴巴的Druid连接池
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,包括数据库地址、端口、数据库名以及连接参数
    url: jdbc:mysql://192.168.6.141:3306/user
    username: remote
    password: fujianz123
  # 从数据库1配置
  db_1:
    # 数据源类名,与主数据库相同
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名,与主数据库相同
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,与主数据库类似,但端口不同
    url: jdbc:mysql://192.168.6.142:3306/user
    username: remote
    password: fujianz123

rules:
  - !SHARDING
    tables:
      dict:
        actualDataNodes: db_${0..1}.dict
        # 数据库分片策略
        databaseStrategy:
          standard:
            shardingColumn: name
            shardingAlgorithmName: address_route
        keyGenerateStrategy:
          column: id
          keyGeneratorName: snowflake

    shardingAlgorithms:
      # 配置一个自定义的Standard分片算法
      address_route:
        # 声明使用自定义的算法实现类
        type: CLASS_BASED
        props:
          strategy: STANDARD
          algorithmClassName: com.fjut.shardingsphere.config.ShardingConfig

    #主键生成算法
    keyGenerators:
      snowflake: # 分布式序列算法名称
        type: SNOWFLAKE

  - !SINGLE
    tables:
      #- ds_0.t_single # 加载指定单表
      #- ds_1.* # 加载指定数据源中的全部单表
      - "*.*" # 加载全部单表

# 属性配置
props:
  # 是否显示执行的SQL语句
  sql-show: true

ShardingSphere-Proxy

Sharding-Proxy的用法基本上和Sharding-JDBC完全相同,不同的区别在于:Sharding-Proxy只是单独拧出来部署了而已