ShardingSphere-JDBC 实现分库分表

本文介绍 Sharding-JDBC 的基本概念与应用场景,并通过一个具体案例演示如何实现分库分表,包括配置依赖、创建表结构、定义分片策略及数据源配置。

1. 概述

定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

  • 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
  • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
  • 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。
    在这里插入图片描述

广播表

指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。

分布式ID生成器

Sharding-JDBC提供了UUID、SNOWFLAKE生成器,还支持用户实现自定义id生成器。比如可以实现了type为SEQ的分布式id生成器,调用统一的分布式id服务获取id。

由于扩展ShardingKeyGenerator是通过JDK的serviceloader的SPI机制实现的,因此还需要在resources/META-INF/services目录下配置org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator文件。
文件内容就是SeqShardingKeyGenerator类的全路径名。这样使用的时候,指定分布式主键生成器的type为SEQ就好了。

在这里插入图片描述

参考实现Snowflake算法:

package com.lonk.util;

import java.util.Calendar;

/**
 * @author lonk
 * @create 2021/2/1 16:44
 */
public class SnowFlake {

    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2016, 10, 1);
        calendar.set(11, 0);
        calendar.set(12, 0);
        calendar.set(13, 0);
        calendar.set(14, 0);
        EPOCH = calendar.getTimeInMillis();
    }

    /**
     * 起始的时间戳
     */
    private final static long EPOCH;

    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;

    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;

    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /** 支持的最大数据标识id,结果是31 */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /** 序列在id中占的位数 */
    private final long sequenceBits = 12L;

    /** 机器ID向左移12位 */
    private final long workerIdShift = sequenceBits;

    /** 数据标识id向左移17位(12+5) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /** 时间截向左移22位(5+5+12) */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 工作机器ID(0~31) */
    private long workerId;

    /** 数据中心ID(0~31) */
    private long datacenterId;

    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;

    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;

    //==============================Constructors=====================================
    /**
     * 构造函数
     * @param workerId 工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public SnowFlake(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    // ==============================Methods==========================================
    /**
     * 获得下一个ID (该方法是线程安全的)
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        //如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }

        //上次生成ID的时间截
        lastTimestamp = timestamp;

        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - EPOCH) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }
}

功能与概念这里不做过多介绍,详细可参考官网,下来通过一个例子说明。

2、实现分库分表案例

pom依赖

  <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-core</artifactId>
            <version>4.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.44</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>

创建三个库:

  • 用户库:db_user

创建用户表

CREATE TABLE `t_user`  (
  `id` int NOT NULL,
  `name` varchar(255)NULL DEFAULT NULL,
  `age` int NOT NULL DEFAULT 0 COMMENT '年龄',
  PRIMARY KEY (`id`),
  INDEX `c_index`(`name`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4;
  • 订单库: db_order_0\db_order_1
    每个库创建两张表:
CREATE TABLE `t_order_0`  (
  `id` bigint NOT NULL,
  `order_id` bigint NULL DEFAULT NULL,
  `user_id` bigint NULL DEFAULT NULL,
  `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

CREATE TABLE `t_order_1`  (
  `id` bigint NOT NULL,
  `order_id` bigint NULL DEFAULT NULL,
  `user_id` bigint NULL DEFAULT NULL,
  `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
2.1 db_user不分库测试

这里先配置db_user不走分库数据源测试一下:

@Configuration
@MapperScan(basePackages = "com.lonk.mapper.nosharding", sqlSessionTemplateRef = "sqlSessionTemplate")
public class DataSourceConfig {

    @Bean(name = "dataSource")
    public DataSource testDataSource() {
        BasicDataSource result = new BasicDataSource();
        result.setDriverClassName(com.mysql.jdbc.Driver.class.getName());
        result.setUrl(String.format("jdbc:mysql://%s:%s/%s?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8", "localhost", "3306", "db_user"));
        result.setUsername("***");
        result.setPassword("***");
        return result;
    }

    /**
     * 获取sqlSessionFactory实例
     *
     * @param shardingDataSource
     * @return
     * @throws Exception
     */
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource shardingDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(shardingDataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/nosharding/*.xml"));
        return bean.getObject();
    }


    @Bean(name = "sqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}
2.2 订单分库分表配置
2.2.1 分片配置

Sharding提供了5种分片策略

  • StandardShardingStrategyConfiguration:
    标准分片策略, 提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持;StandardShardingStrategy 只支持单分片键,PreciseShardingAlgorithm 和RangeShardingAlgorithm 两个分片算法

  • ComplexShardingStrategyConfiguration:
    复合分片策略, 提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。
    ComplexShardingStrategy 支持多分片键;

  • InlineShardingStrategyConfiguration:
    Inline表达式分片策略, 使用Groovy的Inline表达式,提供对SQL语句中的=和IN的分片操作支持

  • HintShardingStrategyConfiguration:
    通过Hint而非SQL解析的方式分片的策略;
    用于处理使用Hint行分片的场景;
    主要是为了应对分片字段不存在SQL中、数据库表结构中,而存在于外部业务逻辑,

  • NoneShardingStrategyConfiguration:
    不分片的策略

Sharding提供了以下4种算法接口
  • PreciseShardingAlgorithm
  • RangeShardingAlgorithm
  • HintShardingAlgorithm
  • ComplexKeysShardingAlgorithm

例子中采用标准分片配置策略的PreciseShardingAlgorithm分别配置分库与分表规则:

分库规则:

@Slf4j
@Service("databaseShardingAlgorithm")
public class DatabaseShardingAlgorithm implements PreciseShardingAlgorithm<Long>{

    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
        // 定义分库算法
        // physicDatabase = getShardConfig(physicDatabase, subValue);
        int index = (int) (preciseShardingValue.getValue() % collection.size());

        log.info("----->db 分库:{}", collection);
        String physicDatabase = null;
        if (StringUtils.isBlank(physicDatabase)) {
            int tmp = 0;
            for (String value : collection) {
                if (tmp == index) {
                    physicDatabase = value;
                    break;
                }
                tmp++;
            }
        }
        log.info("----->分片键是={},逻辑表是={},分片值是={}",preciseShardingValue.getColumnName(),preciseShardingValue.getLogicTableName(),preciseShardingValue.getValue());
        return physicDatabase;
    }
}

分表规则:

@Slf4j
@Service("commonTableShardingAlgorithm")
public class CommonTableShardingAlgorithm implements PreciseShardingAlgorithm<Long> {

    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
        // physicsTable= setValue(preciseShardingValue);
        log.info("----->db 分表:{}", collection);
        String physicsTable = null;

        int index = (int) (preciseShardingValue.getValue() % collection.size());
        if (StringUtils.isBlank(physicsTable)) {
            int tmp = 0;
            for (String value : collection) {
                if (tmp == index) {
                    physicsTable = value;
                    break;
                }
                tmp++;
            }
        }
        log.info("----->分片键是={},逻辑表是={},分片值是={}",preciseShardingValue.getColumnName(), preciseShardingValue.getLogicTableName(), preciseShardingValue.getValue());
        return physicsTable;
    }

}
2.2.2 数据源配置
@Configuration
@MapperScan(basePackages = "com.lonk.mapper.sharding", sqlSessionTemplateRef = "sqlSessionTemplate")
public class SharedDataSourceConfig {
	
	// 两个库名
    private String shardOrder0DataSource="db_order_0";
    private String shardOrder1DataSource="db_order_1";
	
	// 订单逻辑表,在map.xml的sql中的表名
    private String ordersLogicTable="t_order";
    // 数据节点
    private String ordersActualDataNodes = "db_order_$->{0..1}.t_order_$->{0..1}";

    // 分库字段:这个采用用户ID
    private String databaseShardingColumn = "user_id";
    // 分表字段:使用订单ID
    private String ordersShardingColumn = "order_id";

	
    private TableRuleConfiguration getOrderTableRuleConfiguration() {
        // 逻辑表、实际节点
        TableRuleConfiguration orderTableRuleConfig=new TableRuleConfiguration(ordersLogicTable, ordersActualDataNodes);

        // 采用标准分片策略
        orderTableRuleConfig.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration(databaseShardingColumn, new DatabaseShardingAlgorithm()));
        orderTableRuleConfig.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration(ordersShardingColumn,new CommonTableShardingAlgorithm()));

        // 也可通过表达式分片
        // orderTableRuleConfig.setDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration(databaseShardingColumn,"ds${user_id % 2}"));
        // orderTableRuleConfig.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration(ordersShardingColumn, "t_order_${order_id % 2}"));
        
        return orderTableRuleConfig;
    }



    @Bean(name = "shardingDataSource")
    DataSource getShardingDataSource() throws SQLException {
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        // 订单逻辑表
        shardingRuleConfig.getBindingTableGroups().add(ordersLogicTable);
        // 订单配置表规则
        shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());

        // 默认自增主键生成器
        // 指定自增字段以及key的生成方式
        // new UUIDShardingKeyGenerator();
        // new SnowflakeShardingKeyGenerator();
        // orderTableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "id"));
        // shardingRuleConfig.setDefaultKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "id"));
        //  配置广播表规则列表
        // shardingRuleConfig.getBroadcastTables().add("t_config");


        // 通用匹配规则 TableRuleConfiguration
        // shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration(databaseShardingColumn,  databaseShardingAlgorithm));
        // shardingRuleConfig.setDefaultTableShardingStrategyConfig(new StandardShardingStrategyConfiguration(ordersShardingColumn, commonTableShardingAlgorithm));
        // 配置默认分库规则(不配置分库规则,则只采用分表规则)
        shardingRuleConfig.setDefaultTableShardingStrategyConfig(new NoneShardingStrategyConfiguration());
        shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new NoneShardingStrategyConfiguration());
        // 默认库
        shardingRuleConfig.setDefaultDataSourceName(shardOrder0DataSource);

        Properties properties = new Properties();
        properties.setProperty("sql.show",Boolean.TRUE.toString());


        // 读写分离配置
        // MasterSlaveRuleConfiguration masterSlaveRuleConfig = new MasterSlaveRuleConfiguration(
        //        "ms_ds",
        //        shardOrder0DataSource,
        //        Arrays.asList(shardOrder1DataSource),
        //        new LoadBalanceStrategyConfiguration(new RoundRobinMasterSlaveLoadBalanceAlgorithm ().getType())
        //        );
        // shardingRuleConfig.getMasterSlaveRuleConfigs().add(masterSlaveRuleConfig);

        return ShardingDataSourceFactory.createDataSource(createDataSourceMap(), shardingRuleConfig, properties);
    }

    @Bean
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("shardingDataSource") DataSource shardingDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(shardingDataSource);
        // bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis/mybatis-config.xml"));
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/sharding/*.xml"));
        return bean.getObject();
    }

    @Bean
    public DataSourceTransactionManager transactionManager(@Qualifier("shardingDataSource") DataSource shardingDataSource) {
        return new DataSourceTransactionManager(shardingDataSource);
    }

    @Bean("sqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)  {
        return new SqlSessionTemplate(sqlSessionFactory);
    }


	   private Map<String, DataSource> createDataSourceMap() {
        Map<String, DataSource> result = new HashMap<>(2);
        result.put(shardOrder0DataSource, createDataSource(shardOrder0DataSource));
        result.put(shardOrder1DataSource, createDataSource(shardOrder1DataSource));
        return result;
    }
    private DataSource createDataSource(String dataSourceName) {
        BasicDataSource result = new BasicDataSource();
        result.setDriverClassName(com.mysql.jdbc.Driver.class.getName());
        result.setUrl(String.format("jdbc:mysql://%s:%s/%s?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8", "localhost", "3306", dataSourceName));
        result.setUsername("***");
        result.setPassword("***");
        return result;
    }
}

ShardingSphere还支持分片事务与分布式事务与治理,后续再做补充。

demo的github地址

参考:

Apache ShardingSphere 2.x 官方中文文档

ShardingSphere官网

扩展:

Mycat1.6 数据库中间件

TDDL dynamic datasource 入门与使用

分布式数据库中间件 TDDL

<think>首先,用户的问题是:“我想使用ShardingSphere-JDBC实现按月份分库分表的配置方法 请问ShardingSphere-JDBC 按月份分库分表配置示例”。这是一个关于ShardingSphere-JDBC的具体配置问题,用户想要按月份分库分表。 根据系统级指令: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层要求: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用: - [^1] 提到了ShardingSphere-JDBC分库分表实战。 - [^2] 讨论了SpringBoot集成ShardingSphere-JDBC实现分库分表- [^3] 解释了ShardingSphere-JDBC的原理。 - [^4] 提供了基于ShardingSphere-JDBC的MySQL分库分表,包括分页查询慢的解决思路。 - [^5] 展示了基于SpringBoot和ShardingSphere-jdbc5.0.0的分库分表配置,包括yml配置和代码方式。 用户的需求是“按月份分库分表”,所以我需要提供一个配置示例。基于引用[^5],它提到了使用yml配置分库分表,包括行表达式策略和自定义算法类。 关键点: - 分库分表通常基于分片键(sharding key),比如时间戳。 - 按月份分表,意味着每个月的数据存储在不同的表或库中。 - ShardingSphere支持通过行表达式(Inline)或自定义算法(Class-based)来实现。 在引用[^5]中,有示例配置: ```yaml sharding-algorithms: database-inline: type: CLASS_BASED props: strategy: STANDARD algorithmClass‐Name: cloud.cloudwings.hwq.third.common.sharding.DbStandardShardingAlgorithm ``` 这显示了自定义算法类的使用。 对于按月份分表,我可以建议使用时间戳作为分片键,并编写一个自定义的ShardingAlgorithm来实现按月份分片。 步骤: 1. **理解需求**:用户要按月份分库分表。这意味着基于日期字段(如create_time)将数据路由到不同的表或库。 2. **配置方法**: - 在yml配置文件中定义数据源、分片规则。 - 使用自定义分片算法来处理月份逻辑。 3. **示例配置**:提供一个Spring Boot的yml配置示例。 4. **自定义算法**:编写一个Java类实现ShardingSphere的接口。 5. **注意事项**:如引用[^4]提到的分页查询慢的问题,可能需要优化。 现在,构建回答结构: - **引言**:解释按月份分库分表的概念和好处。 - **配置步骤**: - 添加依赖。 - 配置数据源。 - 定义分片规则。 - 实现自定义分片算法。 - **示例代码**:提供yml配置和Java类示例。 - **注意事项**:讨论潜在问题如分布式事务、分页查询。 - **生成相关问题**:在最后添加相关问题。 引用标识:在引用站内内容时添加[^1]、[^2]等。 例如,在提到ShardingSphere-JDBC介绍时引用[^1],在讨论SpringBoot集成时引用[^2]。 用户问题中没有直接引用数学表达式,所以可能不需要使用LaTeX数学格式。但如果涉及计算,比如分片算法中的表达式,可以使用行内数学格式。 例如,在描述分片逻辑时,可以用$month = extract_month(create_time)$。 现在,草拟回答: ### 回答 为了使用ShardingSphere-JDBC实现按月份分库分表,我将提供一个基于Spring Boot的配置示例。按月份分表意味着根据时间字段(如`create_time`)将数据分布到不同的表,通常用于时间序列数据管理[^1]。 #### 步骤1: 添加依赖 在`pom.xml`中添加ShardingSphere-JDBC和数据库驱动依赖。 ```xml <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.3.2</version> <!-- 使用最新版本 --> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> ``` #### 步骤2: 配置数据源和分片规则 在`application.yml`中配置。假设我们有多个库(db0, db1),每个库有按月分的表(如`order_202301`, `order_202302`)。 ```yaml spring: shardingsphere: datasource: names: ds0, ds1 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/db0?useSSL=false username: root password: root ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/db1?useSSL=false username: root password: root rules: sharding: tables: order: actual-data-nodes: ds$->{0..1}.order_$->{202301..202312} # 示例范围 table-strategy: standard: sharding-column: create_time sharding-algorithm-name: table-monthly sharding-algorithms: table-monthly: type: CLASS_BASED props: strategy: STANDARD algorithmClassName: com.example.sharding.MonthShardingAlgorithm ``` #### 步骤3: 实现自定义分片算法 创建一个Java类`MonthShardingAlgorithm`实现`StandardShardingAlgorithm`接口。 ```java package com.example.sharding; import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue; import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue; import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Collection; import java.util.Properties; public class MonthShardingAlgorithm implements StandardShardingAlgorithm<LocalDateTime> { @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<LocalDateTime> shardingValue) { LocalDateTime date = shardingValue.getValue(); String month = date.format(DateTimeFormatter.ofPattern("yyyyMM")); return "order_" + month; // 返回表名,如order_202301 } @Override public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<LocalDateTime> shardingValue) { // 实现范围查询的分片逻辑,这里简化处理 return availableTargetNames; } @Override public void init(Properties properties) { // 初始化逻辑 } @Override public Properties getProps() { return new Properties(); } @Override public void setProps(Properties properties) { // 设置属性 } } ``` #### 注意事项 - **分片键选择**:确保`create_time`字段存在,并且是时间类型。 - **分布式事务**:ShardingSphere支持分布式事务,但需要额外配置[^2]。 - **分页查询**:分页查询可能变慢,建议使用辅助存储如Elasticsearch优化[^4]。 - **动态表创建**:ShardingSphere不自动创建表,需提前建表或使用动态DDL。 #### 解释 - 在yml中,`actual-data-nodes`定义了物理表节点。 - 自定义算法根据`create_time`计算出月份,并映射到表名。 - 对于库分片,可以类似配置,使用不同算法。 §§相关问题§§ 1. 如何优化ShardingSphere-JDBC的分页查询性能? 2. 在分库分表场景下如何处理分布式事务? 3. ShardingSphere-JDBC支持哪些类型的分片算法? 4. 如何动态添加新的月份表? 5. 按月份分表时,如何处理历史数据的迁移? 在回答中,我引用了: - 在提到分库分表概念时,引用[^1]。 - 在讨论分布式事务时,引用[^2]。 - 在分页查询优化时,引用[^4]。 确保语言为中文。</think>### ShardingSphere-JDBC 按月份分库分表配置示例 按月份分库分表是一种常见的时间序列数据管理策略,特别适用于日志、订单等随时间增长的数据场景。ShardingSphere-JDBC 通过自定义分片算法实现这一需求,以下是基于 Spring Boot 的完整配置示例(以 MySQL 为例)。配置核心思路是使用时间字段(如 `create_time`)作为分片键,通过算法动态路由到不同月份的库或表[^1][^3]。 --- ### 步骤1: 添加 Maven 依赖 在 `pom.xml` 中添加 ShardingSphere-JDBC 和数据库驱动依赖: ```xml <dependencies> <!-- ShardingSphere-JDBC --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.3.2</version> <!-- 推荐最新版本 --> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> ``` --- ### 步骤2: 配置 application.yml 在 `application.yml` 中定义数据源和分片规则。以下示例实现- **分库**:两个库(`ds0`, `ds1`),按月份奇偶路由。 - **分表**:按月分表(如 `order_202301`)。 ```yaml spring: shardingsphere: # 数据源配置 datasource: names: ds0, ds1 # 逻辑数据源名 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/db0?useSSL=false username: root password: root ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/db1?useSSL=false username: root password: root # 分片规则 rules: sharding: tables: order: # 逻辑表名 actual-data-nodes: ds$->{0..1}.order_$->{202301..202312} # 物理表节点范围 database-strategy: standard: sharding-column: create_time # 分片键 sharding-algorithm-name: db-monthly # 分库算法 table-strategy: standard: sharding-column: create_time sharding-algorithm-name: table-monthly # 分表算法 # 分片算法定义 sharding-algorithms: db-monthly: type: CLASS_BASED # 自定义算法类 props: strategy: STANDARD algorithmClassName: com.example.sharding.DbMonthShardingAlgorithm # 分库算法实现类 table-monthly: type: CLASS_BASED props: strategy: STANDARD algorithmClassName: com.example.sharding.TableMonthShardingAlgorithm # 分表算法实现类 # 其他配置 props: sql-show: true # 打印SQL日志 ``` --- ### 步骤3: 实现自定义分片算法 创建分片算法类,处理按月路由逻辑。ShardingSphere 通过 `StandardShardingAlgorithm` 接口实现精确分片。 #### 分库算法示例(`DbMonthShardingAlgorithm.java`) ```java package com.example.sharding; import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue; import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Collection; import java.util.Properties; public class DbMonthShardingAlgorithm implements StandardShardingAlgorithm<LocalDateTime> { @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<LocalDateTime> shardingValue) { // 提取月份并计算库索引(例如:月份为奇数路由到 ds0,偶数路由到 ds1) LocalDateTime createTime = shardingValue.getValue(); int month = createTime.getMonthValue(); int dbIndex = month % 2; // 简单示例:$dbIndex = \text{month} \mod 2$ return "ds" + dbIndex; // 返回数据源名,如 ds0 } @Override public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<LocalDateTime> rangeShardingValue) { // 范围查询分片逻辑(需根据业务实现) return availableTargetNames; // 简化处理:返回所有库 } @Override public void init(Properties properties) {} @Override public Properties getProps() { return new Properties(); } } ``` #### 分表算法示例(`TableMonthShardingAlgorithm.java`) ```java package com.example.sharding; import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue; import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Collection; import java.util.Properties; public class TableMonthShardingAlgorithm implements StandardShardingAlgorithm<LocalDateTime> { @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<LocalDateTime> shardingValue) { // 生成表后缀(格式:yyyyMM) LocalDateTime createTime = shardingValue.getValue(); String tableSuffix = createTime.format(DateTimeFormatter.ofPattern("yyyyMM")); return "order_" + tableSuffix; // 返回物理表名,如 order_202301 } @Override public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<LocalDateTime> rangeShardingValue) { // 范围查询分片逻辑(需遍历所有可能表) return availableTargetNames; } @Override public void init(Properties properties) {} @Override public Properties getProps() { return new Properties(); } } ``` --- ### 关键配置说明 1. **分片键选择**:必须使用时间类型字段(如 `create_time`),确保精确提取月份[^3]。 2. **动态表管理**: - `actual-data-nodes` 需预先定义表范围(如 `order_$->{202301..202312}`),但实际表名由算法动态生成。 - 新月份表需提前创建(可通过定时任务自动化)。 3. **算法灵活性**: - 行表达式(`INLINE`)适用于简单规则,但月份分片需自定义类(`CLASS_BASED`)[^5]。 - 分库逻辑可扩展(例如:按年份分库,月份分表)。 4. **性能优化**: - 范围查询(如 `BETWEEN`)需在算法中实现多表路由。 - 分页查询建议结合 Elasticsearch 等二级存储优化[^4]。 --- ### 注意事项 - **分布式事务**:跨库操作需启用 `XA` 或 `Seata` 事务管理器[^2]。 - **数据均衡**:如果月份数据量不均,可结合哈希算法分散热点(如 `month % 4`)。 - **历史数据迁移**:旧数据迁移需停写,通过 `INSERT INTO ... SELECT` 操作。 - **版本兼容性**:ShardingSphere 5.x+ 完全兼容 MyBatis/MyBatis-Plus[^3]。 通过此配置,插入数据时 ShardingSphere 会自动根据 `create_time` 路由到对应库表,例如: - `create_time = '2023-05-15'` → 路由到 `ds1.order_202305`(因 5 是奇数)。 - 查询时无需修改 DAO 层代码,保持 ORM 框架透明性[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值