• SpringBoot集成ShardingSphere分表中间件


    ShardingSphere简介

    ShardingSphere 由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的基于数据库作为存储节点的增量功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

    关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动,我们更加注重在原有基础上提供增量,而非颠覆。

    ShardingSphere-JDBC

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

    • 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC

    • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP 等;

    • 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。

    总结 兼容性好 , 目前主流的ORM框架均能够支持、而且只需要在pom文件中引入依赖即可、使用起来非常方便。

    查看更多关于分库分表、读写分离:https://mp.weixin.qq.com/s/aFXZ8rT9g4oj3ZnioC2vkg

    添加依赖

    <!-- 关系型数据库中间件sharding -->
    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
        <version>4.0.1</version>
    </dependency>
    

    配置数据源

    @Configuration
    public class DruidConfig{
    
        @Bean(name = "dynamicDataSource")
        @Primary
        public DynamicDataSource dataSource(DataSource masterDataSource)
        {
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
            setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
            setDataSource(targetDataSources, DataSourceType.DIROSS.name(), "shardingDataSource");
            return new DynamicDataSource(masterDataSource, targetDataSources);
        }
    
        @Bean
        @ConfigurationProperties("spring.shardingsphere.datasource.master")
        public DataSource ossDataSource(DruidProperties druidProperties)
        {
            DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
            return druidProperties.dataSource(dataSource);
        }
    
        @Bean(name = "shardingDataSource")
        public DataSource shardingDataSource(@Qualifier("ossDataSource") DataSource ossDataSource) throws SQLException
        {
            Map<String, DataSource> dataSourceMap = new HashMap<>();
            dataSourceMap.put("master", ossDataSource);//这里指:spring.shardingsphere.datasource.names=master
    
            // 配置分片规则
            ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
            // ts_dir_oss表规则在yml中已配置,代码写法如下:
            /*
                TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration("ts_dir_oss", "master.ts_dir_oss_$->{2021}_$->{1..3}_$->{1..2}");
                // 配置分布式主键生成策略:雪花算法
                tableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "id") );
                // 配置分表策略
                tableRuleConfig.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("point_time", new PointTimePreciseShardingAlgorithm()));
                shardingRuleConfig.getTableRuleConfigs().add(tableRuleConfig);
            */
            // 获取数据源对象
            DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties());
            return dataSource;
        }
    }
    /**
     * 数据源
     */
    public enum DataSourceType
    {
        /**
         * 主库
         */
        MASTER,
        /**
         * 从库
         */
        SLAVE,
        /**
         * 分表
         */
        DIROSS
    }
    /**
     * 测试查询分表数据
     */
    @DataSource(DataSourceType.DIROSS)
    @GetMapping("/test")
    public void test() {
        // 查询分表数据表时切换数据源
    }
    

    添加yml配置

    spring:
        main:
            # 一个实体类对应多张表,必须设置这个
            allow-bean-definition-overriding: true
        shardingsphere:
            props:
                sql:
                    # 打印SQL
                    show: false
            datasource:
                names: master
                master:
                    type: com.alibaba.druid.pool.DruidDataSource
                    driver-class-name: com.mysql.cj.jdbc.Driver
                    url: jdbc:mysql://localhost:3306/times_tool?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                    username: root
                    password: 123456
            sharding:
                tables:
                    ts_dir_oss:
                        # 配置表规则
                        actual-data-nodes: master.ts_dir_oss_$->{2013..2023}_$->{(1..12).collect{t ->t.toString().padLeft(2,'0')}}_$->{(1..2).collect{t ->t.toString().padLeft(2,'0')}}
                        # 配置主键id生成策略,指定雪花算法
                        key-generator:
                            column: id
                            type: SNOWFLAKE
                        table-strategy:
                            standard:
                                # 分片字段
                                sharding-column: point_time
                                # 标准策略 + 精确分片算法 SQL就是(=或in)
                                precise-algorithm-class-name: com.cn.framework.datasource.PointTimePreciseShardingAlgorithm
                                # 标准策略 + 范围分片算法  主要是(between A and B)
                                range-algorithm-class-name: com.cn.framework.datasource.PointTimeRangeShardingAlgorithm
                            # 复合分片策略  提供对SQL语句中的(=,in,beteewn A and B)的分片操作支持
    #                        complex:
    #                            sharding-columns: point_time
    #                            algorithm-class-name: com.cn.framework.datasource.ComplexShardingAlgorithm
    
    

    代码实现:一张表存储半月数据

    复合分片策略

    /**
     * 复合分片策略  提供对SQL语句中的(=,in,beteewn A and B)的分片操作支持.
     */
    @Slf4j
    public class ComplexShardingAlgorithm implements ComplexKeysShardingAlgorithm {
        
        @Override
        public Collection<String> doSharding(Collection collection, ComplexKeysShardingValue complexKeysShardingValue) {
            Set<String> tables = new HashSet<>();
            Map<String, Range<String>> rangeMap = complexKeysShardingValue.getColumnNameAndRangeValuesMap();
            if(CollUtil.isNotEmpty(rangeMap)){
                for(String s : rangeMap.keySet()){
                    Range<String> valueRange = rangeMap.get(s);
                    if(valueRange!=null){
                        Date begin = DateUtil.parse(valueRange.lowerEndpoint(), DateUtils.YYYY_MM);
                        Date end = DateUtil.parse(valueRange.upperEndpoint(),DateUtils.YYYY_MM);
                        log.info("lowerSuffix:{},upperSuffix:{}",begin,end);
                        for(Object tab:collection){
                            String tableName = (String)tab;
                            String dateStr = StrUtil.subAfter(tableName, "ts_dir_oss_", true).replace("_", "-");
                            Date date = DateUtil.parse(dateStr,DateUtils.YYYY_MM);
                            if((date.after(begin) || date.equals(begin)) && (date.before(end) || date.equals(end) )){
                                tables.add(tableName);
                            }
                        }
                    }
                }
            }
            Map<String, List<String>> map = complexKeysShardingValue.getColumnNameAndShardingValuesMap();
            if(CollUtil.isNotEmpty(map)){
                for(String s : map.keySet()){
                    List<String> list = map.get(s);
                    if(list.size()>0){
                        String v = list.get(0);
                        Date date = DateUtil.parseDate(v);
                        String suffix = getSuffixByDate(date);
                        for(Object tab: collection){
                            String tableName = (String)tab;
                            if(tableName.endsWith(suffix)){
                                tables.add(tableName);
                            }
                        }
                    }
                }
            }
            log.info("match tableNames:{}", tables.toString());
            return tables;
        }
        
        public String getSuffixByDate(Date date){
            
            String dateStr = DateUtil.format(date,"yyyy_MM");
            int day = DateUtil.date(date).dayOfMonth();
            if(day<=15) {
                return dateStr + "_01";
            }else{
                return dateStr + "_02";
            }
        }
    }
    

    标准策略+精确分片算法

    /**
     * 标准策略 + 精确分片算法 SQL就是(=或in)
     */
    @Slf4j
    public class PointTimePreciseShardingAlgorithm implements PreciseShardingAlgorithm<String> {
        
        @Override
        public String doSharding(Collection<String> availableTargetName, PreciseShardingValue<String> preciseShardingValue) {
            Date date = DateUtil.parseDate(preciseShardingValue.getValue());
            String suffix = getSuffixByDate(date);
            for(String tabName: availableTargetName){
                if(tabName.endsWith(suffix)){
                    log.info("match tableName:{}", tabName);
                    return tabName;
                }
            }
            throw new IllegalArgumentException("未找到匹配的数据表");
        }
        public String getSuffixByDate(Date date){
            
            String dateStr = DateUtil.format(date,"yyyy_MM");
            int day = DateUtil.date(date).dayOfMonth();
            if(day<=15) {
                return dateStr + "_01";
            }else{
                return dateStr + "_02";
            }
        }
    }
    

    标准策略+范围分片算法

    /**
     * 标准策略 + 范围分片算法  主要是(between A and B)
     */
    @Slf4j
    public class PointTimeRangeShardingAlgorithm implements RangeShardingAlgorithm<String> {
        
        @Override
        public Collection<String> doSharding(Collection<String> availableTargetName, RangeShardingValue<String> rangeShardingValue) {
            
            Range<String> valueRange = rangeShardingValue.getValueRange();
            Date begin = DateUtil.parse(valueRange.lowerEndpoint(), DateUtils.YYYY_MM);
            Date end = DateUtil.parse(valueRange.upperEndpoint(),DateUtils.YYYY_MM);
            log.info("lowerSuffix:{},upperSuffix:{}",begin,end);
            Set<String> tables = new HashSet<>();
            for(String tableName:availableTargetName){
                String dateStr = StrUtil.subAfter(tableName, "ts_dir_oss_", true).replace("_", "-");
                Date date = DateUtil.parse(dateStr,DateUtils.YYYY_MM);
                if((date.after(begin) || date.equals(begin)) && (date.before(end) || date.equals(end) )){
                    tables.add(tableName);
                }
            }
            log.info("match tableNames:{}", tables.toString());
            return tables;
        }
    }
    

    批量生成数据表

    SQL脚本

    table.template

    CREATE TABLE IF NOT EXISTS ts_dir_oss_${tableSuffix} (
      `id` BIGINT(20) AUTO_INCREMENT NOT NULL,
      `nodes_id` bigint(20) DEFAULT NULL COMMENT '目录节点id',
      `point_id` varchar(48) DEFAULT NULL COMMENT '测点名称',
      `point_time` varchar(18) DEFAULT NULL COMMENT '测点时间',
      `path` varchar(225) DEFAULT NULL COMMENT 'OSS地址',
      `size` bigint(11) DEFAULT NULL COMMENT '文件大小',
      `create_time` datetime DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `index_point_id` (`point_id`),
      KEY `index_nodes_id` (`nodes_id`),
      KEY `index_point_time` (`point_time`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    根据时间范围批量创建数据表

    引入依赖

    <!-- velocity代码生成使用模板 -->
    <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity-engine-core</artifactId>
        <version>${velocity.version}</version>
    </dependency>
    

    代码实现

    @RestController
    @RequestMapping("/tools")
    @Api(tags = "工具")
    public class InitController
    {
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @GetMapping("createTable")
        @ApiOperationSupport(order = 1)
        @ApiOperation("批量创建数据库分表")
        public AjaxResult createTable(@NotEmpty(message = "开始日期不能为空") String beginDate,@NotEmpty(message = "结束日期不能为空") String endDate) throws Exception{
            Date date = DateUtil.parseDate(beginDate);
            while(true) {
                if(date.after(DateUtil.parseDate(endDate))) {
                    break;
                }
                for(int i = 1; i <= 2; i++) {
                    VelocityInitializer.initVelocity();
                    VelocityContext context = new VelocityContext();
                    String tableSuffix = DateUtil.format(date, "yyyy_MM");
                    context.put("tableSuffix", tableSuffix + "_0" + i);
                    Template template = Velocity.getTemplate("template/table.template");
                    StringWriter sw = new StringWriter();
                    template.merge(context, sw);
                    jdbcTemplate.execute(sw.toString());
                }
                date = DateUtils.addMonths(date, 1);
            }
            return AjaxResult.success();
        }
    }
    

    注意事项

    • sharding不支持聚合查询,如 group by统计数据,如果要有统计数据需求,可以用一个统计表实时或定时实现数据同步。
    • 数据表需要根据切片规则提前创建好,否则程序会报错提示找不到指定表。
  • 相关阅读:
    【20220204】连岳摘抄
    【20220208】学会照顾自己,是更大的责任
    【20220205】连岳摘抄
    【20220209】逆向思考的终极解救
    【20220202】连岳摘抄
    【20220207】重新找回节假日
    【20220203】连岳摘抄
    从Hadoop Writable序列化框架到java的序列化原理
    Hadoop Configuration配置类的分析
    从Hadoop Writable序列化框架到java的序列化原理
  • 原文地址:https://www.cnblogs.com/zhaojinhui/p/16731288.html
Copyright © 2020-2023  润新知