分库分表分区总结-sharding-jdbc
简介
Sharding-JDBC 是一个开源项目,由当当网开发并维护,现在已经成为 Apache 的一个孵化项目,名为 Apache ShardingSphere。
提供的功能:
Apache ShardingSphere
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 是随机的,没有趋势递增的特性。
分片算法接口
当我们不用配置文件配置分片算法时,可以使用实现分片算法接口来指定分片算法,配置文件指定算法类
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;
}
}
}

浙公网安备 33010602011771号