学习资料

学习shardingsphere基本原理,看这一篇博客就差不多了,大佬整理的很详细
https://www.cnblogs.com/donleo123/p/17295667.html

实操参考
https://www.cnblogs.com/threee3/p/19085836

https://juejin.cn/post/7371423114381557760

照虎画猫

路由策略就是根据租户号 tenan_id 分库,pv_realtime_data 表根据业务日期 collect_time 按照日期分表

pom配置

  
<dependency>  
    <groupId>org.apache.shardingsphere</groupId>  
    <artifactId>shardingsphere-jdbc-core</artifactId>  
    <version>5.3.2</version>  
</dependency>  
  
<!-- 数据库驱动 -->  
<dependency>  
    <groupId>mysql</groupId>  
    <artifactId>mysql-connector-java</artifactId>  
    <version>8.0.19</version>  
</dependency>  
  
<dependency>  
    <groupId>com.baomidou</groupId>  
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>  
    <version>3.5.5</version>  
</dependency>

yml配置

spring:  
  application:  
    name: db_server  
  datasource:  
    driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver  
    url: jdbc:shardingsphere:classpath:shardingsphere-config.yaml  
  

  
mybatis-plus:  
  configuration:  
    # ???sql??????????????; logback?????  
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

shardingsphere-config配置, 实现根据租户号 tenant_id 字段分库;pv_realtime_data 表再根据 collect_time 字段按日分表

dataSources:  
  # 默认数据源(用于存储租户信息、配置等)  
  ds_default:  
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource  
    driverClassName: com.mysql.cj.jdbc.Driver  
    jdbcUrl: jdbc:mysql://localhost:3306/default_db  
    username: root  
    password: 123456
  
  ds_1000:  
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource  
    driverClassName: com.mysql.cj.jdbc.Driver  
    jdbcUrl: jdbc:mysql://localhost:3306/tenant_1000_db  
    username: root  
    password: 123456
  
  
  # 租户数据源(每个租户独立数据库)  
  ds_1001:  
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource  
    driverClassName: com.mysql.cj.jdbc.Driver  
    jdbcUrl: jdbc:mysql://localhost:3306/tenant_1001_db  
    username: root  
    password: 123456
  
  ds_1002:  
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource  
    driverClassName: com.mysql.cj.jdbc.Driver  
    jdbcUrl: jdbc:mysql://localhost:3306/tenant_1002_db  
    username: root  
    password: 123456

  
# 2. 分片规则配置  
rules:  
  - !SHARDING  
    tables:  
      pv_power_station:  
        # 实际数据节点:动态库 + 动态表  
        actualDataNodes: ds_100$->{['0','1','2','3']}.pv_power_station  
  
        # 分库策略  
        databaseStrategy:  
          standard:  
            shardingColumn: tenant_id  
            shardingAlgorithmName: database-by-tenant  
      pv_device:  
        # 实际数据节点:动态库 + 动态表  
        actualDataNodes: ds_100$->{['0','1','2','3']}.pv_device  
        # 分库策略  
        databaseStrategy:  
          standard:  
            shardingColumn: tenant_id  
            shardingAlgorithmName: database-by-tenant  
  
      pv_realtime_data:  
        # 实际数据节点:动态库 + 动态表  
        actualDataNodes: ds_100$->{['0','1','2','3']}.pv_realtime_data_$->{20250101..20251231}  
  
        databaseStrategy:  
          standard:  
            shardingColumn: tenant_id  
            shardingAlgorithmName: database-by-tenant  
  
        tableStrategy:  
          standard:  
            shardingColumn: collect_time  
            shardingAlgorithmName: table-by-day  
  
    # 3. 分片算法定义  
    shardingAlgorithms:  
      # 3.1 分库算法:按租户ID映射到具体数据库  
      database-by-tenant:  
        type: INLINE  
        props:  
          algorithm-expression: ds_100${tenant_id % 1000}  
          allow-range-query-with-inline-sharding: true  
  
          # 按日分表算法(备选)  
      table-by-day:  
        type: CLASS_BASED  
        props:  
          strategy: standard  
          algorithmClassName: com.jslx.db_server.shardingAlgorithm.DayRangeShardingAlgorithm  
  
  
# 关键配置:自动创建表  
props:  
  sql-show: true  
  # 是否在启动时检查分表元数据一致性  
  check-table-metadata-enabled: false

按日分表的算法 DayRangeShardingAlgorithm 的代码,我的 collect_time 定义的是 LocalDateTime 类型,所以这里的反省也是 LocalDateTime

  
public class DayRangeShardingAlgorithm extends ShardingAlgorithmTool implements StandardShardingAlgorithm<LocalDateTime>  {  
  
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");  
  
    @Override  
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<LocalDateTime> shardingValue) {  
        // 精确分片逻辑(单日查询)  
        String tableSuffix = shardingValue.getValue().format(DATE_FORMATTER);  
        String tableName = shardingValue.getLogicTableName() + "_" + tableSuffix;  
  
        // 检查表是否存在  
        if (!availableTargetNames.contains(tableName)) {  
            throw new IllegalArgumentException("无效的表名: " + tableName);  
        }  
        // shardingTablesCheckAndCreatAndReturn(shardingValue.getLogicTableName(), tableName);  
  
        return tableName;  
    }  
  
    @Override  
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<LocalDateTime> shardingValue) {  
        // 范围查询分片逻辑  
        Set<String> result = new LinkedHashSet<>();  
  
        // 1. 获取查询范围  
        Range<LocalDateTime> range = shardingValue.getValueRange();  
        LocalDateTime lower = range.hasLowerBound() ? range.lowerEndpoint() : null;  
        LocalDateTime upper = range.hasUpperBound() ? range.upperEndpoint() : null;  
  
        // 2. 如果没有范围(全表查询),返回所有可用表(或根据业务限制)  
        if (lower == null && upper == null) {  
            return availableTargetNames;  
        }  
  
        // 3. 如果是单日查询(不跨日)  
        if (isSameDay(lower, upper)) {  
            LocalDateTime targetDate = lower != null ? lower : upper;  
            String tableName = shardingValue.getLogicTableName() + "_" + targetDate.format(DATE_FORMATTER);  
            if (availableTargetNames.contains(tableName)) {  
                result.add(tableName);  
            }  
            return result;  
        }  
  
        // 4. 处理跨日查询  
        LocalDateTime startDate = lower != null ? lower : upper;  
        LocalDateTime endDate = upper != null ? upper : null;  
  
        // 获取日期范围的所有表名  
        Set<String> tablesInRange = getAllDatesInRange(startDate, endDate, shardingValue.getLogicTableName());  
  
        // 过滤出可用的表  
        for (String tableName : tablesInRange) {  
            if (availableTargetNames.contains(tableName)) {  
                result.add(tableName);  
            }  
        }  
  
        return result;  
    }  
  
    /**  
     * 判断两个 LocalDateTime 是否是同一天  
     */  
    private boolean isSameDay(LocalDateTime d1, LocalDateTime d2) {  
        if (d1 == null || d2 == null) return false;  
        return d1.toLocalDate().isEqual(d2.toLocalDate());  
    }  
  
    /**  
     * 生成日期范围内的所有表名  
     */  
    private Set<String> getAllDatesInRange(LocalDateTime start, LocalDateTime end, String logicTableName) {  
        Set<String> tables = new LinkedHashSet<>();  
  
        // 如果只有开始日期没有结束日期,假设查询今天  
        if (end == null) {  
            tables.add(logicTableName + "_" + start.format(DATE_FORMATTER));  
            return tables;  
        }  
  
        LocalDate startDate = start.toLocalDate();  
        LocalDate endDate = end.toLocalDate();  
  
        // 遍历日期范围  
        LocalDate currentDate = startDate;  
        while (!currentDate.isAfter(endDate)) {  
            tables.add(logicTableName + "_" + currentDate.format(DATE_FORMATTER));  
            currentDate = currentDate.plusDays(1);  
        }  
  
        return tables;  
    }  
  
    /**  
     * 可选:支持按日期范围查询,带有限制条件防止查询范围过大  
     */  
    private Set<String> getAllDatesInRangeWithLimit(LocalDateTime start, LocalDateTime end,  
                                                    String logicTableName, int maxDays) {  
        Set<String> tables = new LinkedHashSet<>();  
  
        LocalDate startDate = start.toLocalDate();  
        LocalDate endDate = end.toLocalDate();  
  
        // 检查日期范围是否过大  
        long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate);  
        if (daysBetween > maxDays) {  
            throw new IllegalArgumentException("查询日期范围不能超过 " + maxDays + " 天");  
        }  
  
        // 遍历日期范围  
        LocalDate currentDate = startDate;  
        while (!currentDate.isAfter(endDate)) {  
            tables.add(logicTableName + "_" + currentDate.format(DATE_FORMATTER));  
            currentDate = currentDate.plusDays(1);  
        }  
  
        return tables;  
    }  
}

踩坑

版本问题不说了

启动时有个报错

java.lang.NoSuchMethodError: org.yaml.snakeyaml.representer.Representer: method 'void <init>()' not found

Representer 的无参构造不存在, 按照大佬的方法重写一下Representer,注意包名

https://github.com/yanzhisishui/sharding-jdbc-demo

image