分库分表分区总结-sharding-jdbc

简介

Sharding-JDBC 是一个开源项目,由当当网开发并维护,现在已经成为 Apache 的一个孵化项目,名为 Apache ShardingSphere。

提供的功能:

 

 

Apache ShardingSphere

ShardingSphere 是一个开源的分布式数据库中间件解决方案,由 Apache Software Foundation 维护。它主要包括四个项目:Sharding-JDBC、Sharding-Proxy、Sharding-Sidecar(计划中)和 Sharding-Lite(计划中)。

ShardingSphere 的主要特性包括:

- 数据分片:ShardingSphere 支持对数据库和表进行分片,以提高查询性能和数据管理的灵活性。
- 读写分离:ShardingSphere 支持读写分离,可以将读操作和写操作分发到不同的数据库分片,以提高性能和可用性。
- 分布式事务:ShardingSphere 提供了对分布式事务的支持,包括 XA、BASE 和 TCC 事务。
- 数据库治理:ShardingSphere 提供了包括数据迁移、流量复制、防火墙等一系列的数据库治理功能。

ShardingSphere 的四个项目分别有不同的应用场景:

- Sharding-JDBC:适用于 Java 项目,它在 JDBC 层提供数据分片、读写分离等功能。
- Sharding-Proxy:提供了一个数据库代理,支持任何使用 MySQL/PostgreSQL 协议的项目。
- Sharding-Sidecar(计划中):计划作为 Kubernetes 的一个 Sidecar,提供更为云原生的数据库中间件解决方案。
- Sharding-Lite(计划中):计划提供一个轻量级的数据库中间件解决方案,适用于 IoT、边缘计算等场景。
 

与jdbc的关系

可以将 ShardingSphere-JDBC 看作是 JDBC 的一个扩展或增强版,它在保持 JDBC 的所有功能的同时,提供了一些额外的功能数据库分片、读写分离、分布式事务等、

原理

工作原理主要基于以下几个步骤:

 SQL 解析:当你执行一个 SQL 语句时,ShardingSphere-JDBC 首先会对这个 SQL 语句进行解析,获取到 SQL 语句的类型(例如,查询、更新或删除),以及 SQL 语句涉及的表和列。SQL解析过程分为词法解析和语法解析两步,先用词法解析将SQL拆解成不可再分的原子单元。在根据不同数据库方言所提供的字典,将这些单元归类为关键字,表达式,变量或者操作符等类型。接着语法解析会将拆分后的SQL转换为抽象语法树,通过对抽象语法树遍历,提炼出分片所需的上下文,上下文包含查询字段信息(Field)、表信息(Table)、查询条件(Condition)、排序信息(Order By)、分组信息(Group By)以及分页信息(Limit)等,并标记出 SQL中有可能需要改写的位置。

执行器优化:执器优化对SQL分片条件进行优化,尽量应用到索引。

路由计算:然后,ShardingSphere-JDBC 会根据 SQL 语句中的参数和你定义的分片策略,计算出这个 SQL 语句应该在哪些分片上执行。这个过程称为路由计算。

SQL 改写:接着,ShardingSphere-JDBC 会根据路由计算的结果,将原始的 SQL 语句改写为多个针对具体分片的 SQL 语句。这个过程称为 SQL 改写。

 并行执行:最后,ShardingSphere-JDBC 会并行地在各个分片上执行改写后的 SQL 语句,并收集结果。

 结果合并:对于查询语句,ShardingSphere-JDBC 还会将各个分片的查询结果合并为一个统一的结果。

通过这种方式,ShardingSphere-JDBC 实现了在应用程序层面的数据库分片,使得应用程序可以在不修改代码的情况下,实现数据库的水平分片。同时,由于 ShardingSphere-JDBC 是在 JDBC 层进行操作的,因此它可以与任何支持 JDBC 的数据库一起使用。

 

核心概念

分片

分片(Sharding)是一种数据库架构模式,用于将一个大型数据库分解为更小、更易于管理的部分,称为“分片”。每个分片都是数据库的一个独立实例,包含其自身的数据和索引。分片可以分布在多个服务器上,从而分散了数据库的负载并提高了性能。

分片的主要优点

它可以提高查询性能和数据的可扩展性。由于每个查询只需要在一个或少数几个分片上进行,而不是在整个数据库上进行,因此查询性能可以得到提高。

由于分片可以分布在多个服务器上,因此当数据量增加时,可以通过增加服务器数量来扩展数据库。

分片主要缺点

  分片会增加应用程序的复杂性,

  某些操作,如跨多个分片的联接和事务,可能会变得更加复杂。 

数据节点

数据节点(Data Node)是分库分表中一个不可再分的最小数据单元(表),它由数据源名称和数据表组成。

在分片策略中,数据节点通常被定义为 "数据库名.表名" 的格式。例如,如果你有一个数据库 db0 和 db1,每个数据库中都有表 order0 和 order1,那么你就有四个数据节点:db0.order0、db0.order1、db1.order0 和 db1.order1。

当执行 SQL 语句时,Sharding-JDBC 会根据分片策略和 SQL 语句中的参数,确定应该在哪些数据节点上执行这个 SQL 语句。

这些分片上。

分片策略

分片策略只是抽象出的概念,它是由分片算法和分片健组合而成,分片算法做具体的数据分片逻辑。

每种策略中可以是多个分片算法的组合,每个分片算法可以对多个分片健做逻辑判断。

1. 标准分片策略(Standard Sharding Strategy):这是最常见的分片策略,它需要定义一个精确的分片算法和一个范围的分片算法。精确分片算法是必须实现的,范围分片算法是可选实现的(未实现的话如果有相关查询就会查询所有分片)

2. 复合分片策略(Complex Sharding Strategy):这种策略用于处理多分片键的情况,它需要定义一个复合分片算法。

3. 行表达式分片策略(Inline Sharding Strategy):这种策略使用 Groovy 表达式作为分片算法,适用于简单的分片需求,只能用一个分片键。只需要提供一个包含分片键的表达式,例如 "ds${user_id % 2}" 或 "order${order_id % 2}"。对于范围查询会查询所有分片。

4. Hint 分片策略(Hint Sharding Strategy):这种策略允许在代码中手动指定分片,适用于无法通过 SQL 解析确定分片的特殊情况。

 

分片键

分片键(Sharding Key)用于分片的数据库字段,根据分表键和分片策略可以确定数据存储在哪个分片上。

分片键可以是任何字段,但通常应选择能够均匀分布数据的字段,以避免产生数据倾斜。数据倾斜是指大量的数据都集中在少数几个分片上,这会导致查询性能下降,因为大部分查询都会集中在这些分片上。

分片算法

sharding-jdbc 并没有直接提供分片算法的实现,需要开发者根据业务自行实现。分片算法可以理解成一种路由规则。

精确分片算法(PreciseShardingAlgorithm):用于处理 = 和 IN 这类精确查找的分片。例如,WHERE order_id = 1001 或 WHERE order_id IN (1001, 1002)。

范围分片算法(RangeShardingAlgorithm):用于处理 BETWEEN AND、>、<、>=、<= 这类范围查找的分片。

复合分片算法(ComplexKeysShardingAlgorithm)用于多个字段作为分片键的分片操作,需要在复合分片策略(ComplexShardingStrategy )下使用。

Hint 分片算法(HintShardingAlgorithm):Hint 是一种可以在代码中直接指定数据库或表的分片策略的方式。有些时候我们并没有使用任何的分片键和分片策略,可还想将 SQL 路由到目标数据库和表,就需要通过手动干预指定SQL的目标数据库和表信息,这也叫强制路由。

这两种分片算法可以通过实现 Sharding-JDBC 提供的 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 接口来自定义。

配置说明

spring:
  shardingsphere:
    dataSources: #数据源配置,可以配置多个
      <data_source_name>: #<数据源池实现类> 具体的数据源
        driverClassName: #数据库驱动名
        url: #数据库连接
        username: #数据库名
        password: #数据库密码
    shardingRule:
      tables: #需要进行分表的逻辑表
        <logic_table_name>: #逻辑表名
          actualDataNodes: #描述数据源名称和实际表,分隔符为点,多个数据节点用逗号分隔,支持内联表达式。这意味着只对数据库进行分片。示例:ds${0..7}.tbl${1..0}
          tableStrategy: #表分片策略,如果没有,则使用默认的数据库分片战略。下面的分片策略只能选择一种。
            standard: #单分片列的标准分片场景
              shardingColum: #用于分片的列名称
              preciseAlgorithmClassName: #用于“=”和“IN”的精确算法类名。此类需要实现PreciseShardingAlgorithm,并且需要无参数构造函数
              rangeAlgorithmClassName: #用于“between”之间的范围算法类名。此类需要实现RangeShardingAlgorithm,并且需要无参数构造函数
            complex: #多个分片列的复杂分片场景
              shardingColumns: #分片列的名称。用逗号分隔的多列
              algorithmClassName: #复杂分片算法类名。此类需要实现ComplexKeysShardingAlgorithm,并且需要无参数构造函数
            inline: #单分片列的内联表达式分片场景
              shardingColum: #用于分片的列名称
              algorithmInlineExpression: #切分算法的内联表达式
            hint: #提示切分策略
              algorithmClassName: #提示切分算法类名。这个类需要实现HintShardingAlgorithm,并且需要一个无参数构造函数
            none: #不分片
          databaseStrategy: #数据库分片策略,与表分片策略一样
          keyGenerator:
            column:   #键生成器的列名
            type: #键生成器的类型 SNOWFLAKE或UUID
            props: #关于属性,请注意:当使用雪花时,`worker'。id'和'max.time.difference。需要设置“雪花”的毫秒数。要使用此算法的生成值作为分片值,建议配置“max.vibration.offset”`
      bindingTables:   #绑定表规则配置
    props:
      sql.show: #是否打印sql,默认为false
      executor.size: #工作线程数,默认CPU线程数
      check:
        table:
          metadata:
            enabled: #若要检查所有表的元数据一致性,默认值:false
      max:
        connections:
          size:
            per:
              query: #每个物理数据库的每个查询分配的最大连接数。默认值:1

key-generator 参数

用于配置主键生成策略。当你插入新的记录时,如果没有指定主键的值(指定了它就不生成了),ShardingSphere-JDBC 将使用这个策略来生成主键的值。

如果要自己指定主键,可以不配置这个参数

ShardingSphere-JDBC 提供了两种内置的主键生成策略:

1. SNOWFLAKE:这是一种基于 Twitter 的 Snowflake 算法的主键生成策略。它可以生成全局唯一的、趋势递增的 ID,适用于分布式系统。

2. UUID:这是一种基于 UUID 算法的主键生成策略。它可以生成全局唯一的 ID,但这些 ID 是随机的,没有趋势递增的特性。

column指定主键字段,type指定生成策略
 
 

分片算法接口

当我们不用配置文件配置分片算法时,可以使用实现分片算法接口来指定分片算法,配置文件指定算法类

PreciseShardingAlgorithm

PreciseShardingAlgorithm是一个精确分片算法接口,用于处理=, IN等精确匹配的分片。

availableTargetNames 为配置的数据节点,shardingValue可以拿到路由进来的分片键的值,doSharding方法要返回一个要路由到的数据节点

示例:

public class MyPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {

    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
        for (String dbName : availableTargetNames) {
            if (shardingValue.getValue() % 2 == 0 && dbName.endsWith("0")) {
                return dbName;
            } else if (shardingValue.getValue() % 2 != 0 && dbName.endsWith("1")) {
                return dbName;
            }
        }
        throw new UnsupportedOperationException();
    }
}

 

RangeShardingAlgorithm

RangeShardingAlgorithm是一个范围分片算法接口,用于处理BETWEEN AND,>, <, >=, <=等范围查询的分片。

public class MyRangeShardingAlgorithm implements RangeShardingAlgorithm<Long> {

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> shardingValue) {
        Collection<String> result = new ArrayList<>();
        for (Long i = shardingValue.getValueRange().lowerEndpoint(); i <= shardingValue.getValueRange().upperEndpoint(); i++) {
            for (String each : availableTargetNames) {
                if (each.endsWith(i % 2 + "")) {
                    result.add(each);
                }
            }
        }
        return result;
    }
}

SingleKeyDatabaseShardingAlgorithm 

用来处理基于单个分片键的数据库分片的

接口定义了三个方法:

1. doEqualSharding:处理等值查询,这是 PreciseShardingAlgorithm 的功能。

2. doInSharding:处理 IN 查询,这也可以看作是一种精确查询。

3. doBetweenSharding:处理范围查询,这是 RangeShardingAlgorithm 的功能。

如果你实现了 SingleKeyDatabaseShardingAlgorithm 接口,你就不需要再单独实现 PreciseShardingAlgorithm 和 RangeShardingAlgorithm。

public class MyDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Long> {

    @Override
    public String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<Long> shardingValue) {
        for (String each : availableTargetNames) {
            if (each.endsWith(shardingValue.getValue() % 2 + "")) {
                return each;
            }
        }
        throw new UnsupportedOperationException();
    }

    // 实现其他方法...
}
spring:
  shardingsphere:
    rules:
      sharding:
        default-database-strategy:
          standard:
            sharding-column: id
            sharding-algorithm-name: my_database_sharding_algorithm
        sharding-algorithms:
          my_database_sharding_algorithm:
            type: CLASS_BASED
            props:
              strategy: STANDARD
              algorithmClassName: com.example.MyDatabaseShardingAlgorithm

 

数据库连接池的配置

spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/db0
        username: root
        password: password
        maximum-pool-size: 10

实践

添加依赖

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core</artifactId>
    <version>5.0.0</version>
</dependency>

根据springBoot版本选择对应的shardingjdbc的版本

shardingsphere-jdbc-core-spring-boot-starter 启动器的最小版本是 5.0.0

修改配置文件

spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/db0
        username: root
        password: password
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/db1
        username: root
        password: password
    rules:
      sharding:
        default-database-strategy:
          standard:
            sharding-column: id
            sharding-algorithm-name: database_inline
        default-table-strategy:
          none:
        sharding-algorithms:
          database_inline:
            type: INLINE
            props:
              algorithm-expression: ds${id % 2}

如果使用类指定分片算法需要指定类地址,并编写实现类

spring:
  # shardingJDBC
  shardingsphere:
    datasource:
      names: db1
      db1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/database?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8&tinyInt1isBit=false
        username: root
        password: 123456
    sharding:
      tables:
        table_a:
          actual-data-nodes: db1.table_a
          table-strategy:
            standard:
              sharding-column: created_at
              precise-algorithm-class-name: com.**.service.config.sharding.DateShardingAlgorithm
              range-algorithm-class-name: com.**.service.config.sharding.DateShardingAlgorithm
        table_b:
          actual-data-nodes: db1.table_b
          table-strategy:
            standard:
              sharding-column: created_at
              precise-algorithm-class-name: com.**.service.config.sharding.DateShardingAlgorithm
              range-algorithm-class-name: com.**.service.config.sharding.DateShardingAlgorithm
      defaultDataSourceName: db1
    props:
      sql:
        # 打印 sql
        show: true

分表工具类 ShardingAlgorithmTool

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * 日期分表策略
 */
public class DateShardingAlgorithm extends ShardingAlgorithmTool<Date> {

    /**
     * 获取 指定分表
     */
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> preciseShardingValue) {
        return shardingTablesCheckAndCreatAndReturn(preciseShardingValue.getLogicTableName(), preciseShardingValue.getLogicTableName() + DateUtil.format(preciseShardingValue.getValue(), "_yyyy_MM_dd"));
    }

    /**
     * 获取 范围分表
     */
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> rangeShardingValue) {
        Range<Date> valueRange = rangeShardingValue.getValueRange();
        Date lowerDate = valueRange.lowerEndpoint();
        Date upperDate = valueRange.upperEndpoint();
        List<String> tableNameList = new ArrayList<>();
        for (DateTime dateTime : DateUtil.rangeToList(DateUtil.beginOfDay(lowerDate), DateUtil.endOfDay(upperDate), DateField.DAY_OF_YEAR)) {
            String resultTableName = rangeShardingValue.getLogicTableName() + DateUtil.format(dateTime, "_yyyy_MM_dd");
            if (shardingTablesExistsCheck(resultTableName)) {
                tableNameList.add(resultTableName);
            }
        }
        return tableNameList;
    }
}

分表实现类 DateShardingAlgorithm

import com.**.**.dao.CommonMapper;
import com.**.**.domain.db.CreateTableSql;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;

import java.util.HashSet;
import java.util.List;

/**
 * 分表工具
 */
@Slf4j
public abstract class ShardingAlgorithmTool<T extends Comparable<?>> implements PreciseShardingAlgorithm<T>, RangeShardingAlgorithm<T> {

    private static CommonMapper commonMapper;

    private static final HashSet<String> tableNameCache = new HashSet<>();

    /**
     * 手动注入
     */
    public static void setCommonMapper(CommonMapper commonMapper) {
        ShardingAlgorithmTool.commonMapper = commonMapper;
    }

    /**
     * 判断 分表获取的表名是否存在 不存在则自动建表
     *
     * @param logicTableName  逻辑表名(表头)
     * @param resultTableName 真实表名
     * @return 确认存在于数据库中的真实表名
     */
    public String shardingTablesCheckAndCreatAndReturn(String logicTableName, String resultTableName) {

        synchronized (logicTableName.intern()) {
            // 缓存中有此表 返回
            if (shardingTablesExistsCheck(resultTableName)) {
                return resultTableName;
            }

            // 缓存中无此表 建表 并添加缓存
            CreateTableSql createTableSql = commonMapper.selectTableCreateSql(logicTableName);
            String sql = createTableSql.getCreateTable();
            sql = sql.replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS");
            sql = sql.replace(logicTableName, resultTableName);
            commonMapper.executeSql(sql);
            tableNameCache.add(resultTableName);
        }

        return resultTableName;
    }

    /**
     * 判断表是否存在于缓存中
     *
     * @param resultTableName 表名
     * @return 是否存在于缓存中
     */
    public boolean shardingTablesExistsCheck(String resultTableName) {
        return tableNameCache.contains(resultTableName);
    }

    /**
     * 缓存重载方法
     *
     * @param schemaName 待加载表名所属数据库名
     */
    public static void tableNameCacheReload(String schemaName) {
        // 读取数据库中所有表名
        List<String> tableNameList = commonMapper.getAllTableNameBySchema(schemaName);
        // 删除旧的缓存(如果存在)
        ShardingAlgorithmTool.tableNameCache.clear();
        // 写入新的缓存
        ShardingAlgorithmTool.tableNameCache.addAll(tableNameList);
    }

}

项目启动时将表载入缓存/注入工具类属性 ShardingTablesLoadRunner

import com.**.**.config.sharding.ShardingAlgorithmTool;
import com.**.**.dao.CommonMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 项目启动后 读取已有分表 进行缓存
 */
@Slf4j
@Order(value = 1) // 数字越小 越先执行
@Component
public class ShardingTablesLoadRunner implements CommandLineRunner {

    @Value("${db.schema-name}")
    private String schemaName;

    @Resource
    private CommonMapper commonMapper;

    @Override
    public void run(String... args) throws Exception {

        // 给 分表工具类注入属性
        ShardingAlgorithmTool.setCommonMapper(commonMapper);
        // 调用缓存重载方法
        ShardingAlgorithmTool.tableNameCacheReload(schemaName);

        log.info("ShardingTablesLoadRunner start OK");
    }
}

 Mybatis SQL 映射 CommonMapper

import com.**.**.domain.db.CreateTableSql;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 常用工具 mapper
 */
public interface CommonMapper {

    /**
     * 查询数据库中的所有表名
     *
     * @param schema 数据库名
     * @return 表名列表
     */
    List<String> getAllTableNameBySchema(@Param("schema") String schema);

    /**
     * 查询建表语句
     *
     * @param tableName 表名
     * @return 建表语句
     */
    CreateTableSql selectTableCreateSql(@Param("tableName") String tableName);

    /**
     * 执行SQL
     *
     * @param sql 待执行SQL
     */
    void executeSql(@Param("sql") String sql);

}

Mybatis SQL 映射 XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.**.**.dao.CommonMapper">

    <resultMap id="selectTableCreateSqlResultMap" type="com.**.**.domain.db.CreateTableSql">
        <result column="Table" property="table"/>
        <result column="Create Table" property="createTable"/>
    </resultMap>

    <select id="getAllTableNameBySchema" resultType="java.lang.String">
        SELECT TABLES.TABLE_NAME
        FROM information_schema.TABLES
        WHERE TABLES.TABLE_SCHEMA = #{schema}
    </select>

    <select id="selectTableCreateSql" resultMap="selectTableCreateSqlResultMap">
        SHOW CREATE TABLE ${tableName}
    </select>

    <update id="executeSql">
        ${sql}
    </update>

</mapper>

Mybatis SQL 映射实体

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 建表语句查询结果
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CreateTableSql {

    private String table;

    private String createTable;
}

其他

标准分片策略非精确和范围路由

将走全数据节点查询并合并结果

动态数据节点配置

对于按时间进行分表,数据节点会随着时间变化,动态的增加数据节点,配置到配置文件的数据节点要跟着变化才行。

正常情况下配置到配置文件的数据节点,再项目启动的时候进行加载,以后执行sql的时候就以配置的为准

想要动态的设置数据接口,可以用一下配置:

spring:
  shardingsphere:
    datasource:
      names: xgss
      xgss:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: ${spring.datasource.url}
        username: ${spring.datasource.username}
        password: ${spring.datasource.password}
        minimum-idle: ${spring.datasource.hikari.minimum-idle:1}
        maximum-pool-size: ${spring.datasource.hikari.maximum-pool-size:20}
        max-lifetime: ${spring.datasource.hikari.max-lifetime:3000}
        idle-timeout: ${spring.datasource.hikari.idle-timeout:3000}
    sharding:
      tables:
        mytable:
          actual-data-nodes: xgss.mytable
          table-strategy:
            standard:
              sharding-column: day
              precise-algorithm-class-name: com.xgss.algorithm.StatementDayDetailPreciseSharding
              range-algorithm-class-name: com.xgss.algorithm.StatementDayDetailRangeSharding

名称为xgss的数据圈的mytable为做了按年的分表(mytable_2023),actual-data-node配置上逻辑表名即可(防止启动报错),分片字段为day(2023-01-01)

编写一个定时任务,用来定时的刷新数据节点

@Slf4j
@Component
@EnableScheduling
public class RefreshShardingActualDataNodes {
    @Resource
    private DataSource dataSource;

    //12月31号1点执行一次
    @Scheduled(cron = "0 0 1 31 12 ?")
    private void handler(){
        initTables();
    }

    //每次项目启动执行一次
    @PostConstruct
    void init() {
        initTables();
    }

    private void initTables(){
        try{
            ShardingDataSource dataSource = (ShardingDataSource) this.dataSource;
            ShardingRule rule = dataSource.getRuntimeContext().getRule();
            refreshStatementDayDetail(rule);
        }catch (Exception e){
            log.error("刷新分表数据节点失败",e);
        }

    }

    void refreshStatementDayDetail(ShardingRule rule) throws Exception{
        TableRule tbRule = rule.getTableRule("mytable");
        Field actualDataNodesField = TableRule.class.getDeclaredField("actualDataNodes");
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(actualDataNodesField,actualDataNodesField.getModifiers()& ~Modifier.FINAL);
        actualDataNodesField.setAccessible(true);
        List<DataNode> newDataNodes=new ArrayList<>();
        Map<DataNode,Integer> dateNodeIntegerMap= Maps.newHashMap();
        int endYear = DateUtil.thisYear();
        //该表月初会建立下一年的表,如果是12月分把下一年的表也加入到节点列表
        int month = DateUtil.month(new Date());
        if(month==12){
            endYear=endYear+1;
        }
        Set<String> actualTables= Sets.newHashSet();
        AtomicInteger a=new AtomicInteger(0);
        int start=2021;//最早的表的后缀为2021
        for(int i = start; i<= endYear; i++){
            DataNode dataNode=new DataNode("xgss.mytable_"+i);
            newDataNodes.add(dataNode);
            actualTables.add(dataNode.getTableName());
            dateNodeIntegerMap.put(dataNode,a.intValue());
            a.incrementAndGet();
        }
        log.info("分表数据节点刷新:{}", JSONUtil.toJsonStr(actualTables));
        actualDataNodesField.set(tbRule,newDataNodes);
        Field actualTablesField = TableRule.class.getDeclaredField("actualTables");
        actualTablesField.setAccessible(true);
        actualTablesField.set(tbRule,actualTables);
        //            List<DataNode> dataNodes = tbRule.getActualDataNodes();
        Field dataNodeIndexMapField = TableRule.class.getDeclaredField("dataNodeIndexMap");
        dataNodeIndexMapField.setAccessible(true);
        dataNodeIndexMapField.set(tbRule,dateNodeIntegerMap);

        Map<String, Collection<String>> datasourceToTablesMap=Maps.newHashMap();
        datasourceToTablesMap.put("xgss",actualTables);
        Field datasourceToTablesMapField = TableRule.class.getDeclaredField("datasourceToTablesMap");
        datasourceToTablesMapField.setAccessible(true);
        datasourceToTablesMapField.set(tbRule,datasourceToTablesMap);

    }
}

这样即可,每次启动项目时会根据当前时间判断出一共有哪些表,刷新数据节点的配置;当快要用到新的表的时候,即使不进行重启项目,定时任务也会把新的数据节点刷新进去。

集成sharding-jdbc后k8s健康检查报错

org.apache.shardingsphere.shardingjdbc.jdbc.unsupported.AbstractUnsupportedOperationConnection.isValid

新版Spring中,Spring数据源健康检查用到 sharding jdbc 时,该组件没有完全实现MySQL驱动导致的问题
是由于 ShardingDataSource 内部是封装了真实数据源的,所以 ShardingDataSource 并不需要进行健康检查

@Configuration
public class DataSourceHealthConfig{

    /**
     * 解决新版Spring中,集成sharding jdbc 健康检查异常
     */
    @Bean
    DataSourcePoolMetadataProvider dataSourcePoolMetadataProvider() {
        return dataSource -> dataSource instanceof HikariDataSource
                ? new HikariDataSourcePoolMetadata((HikariDataSource) dataSource)
                : new NotAvailableDataSourcePoolMetadata();
    }

    /**
     * 不可用的数据源池元数据.
     */
    private static class NotAvailableDataSourcePoolMetadata implements DataSourcePoolMetadata {
        @Override
        public Float getUsage() {
            return null;
        }

        @Override
        public Integer getActive() {
            return null;
        }

        @Override
        public Integer getMax() {
            return null;
        }

        @Override
        public Integer getMin() {
            return null;
        }

        @Override
        public String getValidationQuery() {
            return "select 1";
        }

        @Override
        public Boolean getDefaultAutoCommit() {
            return null;
        }
    }
}

 

posted @ 2023-07-28 17:05  星光闪闪  阅读(572)  评论(0)    收藏  举报