shardingsphere按年月分库分表实战

 前言:

 

        由于业务订单增长,设计之初没有考虑到订单量会达到亿级以上,从而导致现在订单服务查询耗时太久,为解决该问题,并结合业务实际情况,通过调研最终采用shardingsphere分库分表架构对订单数据按年分库,按业务code和月分表,最终分布到每张表的数据量尽可能的减少到百万以内,优化查询效果。

关于shardingsphere的介绍和基本使用可参考ShardingSphere介绍与使用_还没秃的小菜鸡的博客-CSDN博客_shardingsphere

官网地址:  概览 :: ShardingSphere

目前shardingshpere支持5.0以上版本,为了避免高版本带来的一些坑,作者这里选用4.1.1 的springboot起步依赖包

pom依赖如下:

<dependency>
  <groupId>org.apache.shardingsphere</groupId>
  <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
  <version>4.1.1</version>
</dependency>

数据库表结构:

CREATE TABLE `insurance_order` (
 `subOrderId` varchar(128) NOT NULL COMMENT '子订单',
 `orderId` varchar(128) NOT NULL COMMENT '保险订单号',
 `otaOrderId` varchar(128) NOT NULL COMMENT 'ota的主订单',
 `merchantId` varchar(32) NOT NULL COMMENT '商户Id',
 `insurCompanyCode` varchar(32) NOT NULL COMMENT '保险公司id',
 `policyNo` varchar(128) NOT NULL DEFAULT '' COMMENT '保单号',
 `proposalNo` varchar(128) NOT NULL DEFAULT '' COMMENT '核保订单号',
 `insurType` varchar(32) NOT NULL DEFAULT '' COMMENT '保险类别',
 `productCode` varchar(32) NOT NULL DEFAULT '' COMMENT '保险产品代码',
 `premium` double(10,2) NOT NULL DEFAULT '0.00' COMMENT '总保费',
 `amount` double(20,2) NOT NULL DEFAULT '0.00' COMMENT '保额',
 `createTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '订单创建时间'
。。。部分省略
 PRIMARY KEY (`subOrderId`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='保险订单';

核心代码:

@SpringBootConfiguration
public class InsuranceOrderShardingConfig {

    //逻辑表名称
    private final String LOGICAL_TABLE = "insurance_order";
   	//数据库分片键
    private final String DATABASE_SHARDING_COLUMN = "createTime";
    //表分片键
    private final String TABLE_SHARDING_COLUMN = "createTime,insurCompanyCode";

    @Bean
    DataSource getShardingDataSource() throws SQLException {
        // 分片规则配置对象
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        // 规则配置
        shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());
        // 默认库,如果存在广播表和绑定表也可在此配置
        shardingRuleConfig.setDefaultDataSourceName("default_dataSource");
        final Properties properties = new Properties();
        // 开启日志打印
        properties.setProperty("sql.show", "true");
        // 数据源配置
        return ShardingDataSourceFactory.createDataSource(createDataSourceMap(), shardingRuleConfig, properties);
    }

	 //定义物理表建表规则
     // 关于inline表达式,可查看官方文档 		    https://shardingsphere.apache.org/document/legacy/4.x/document/cn/features/sharding/other-features/inline-expression/
    private TableRuleConfiguration getOrderTableRuleConfiguration() {
        String expression = "2020." + LOGICAL_TABLE + "_${['pingan','taibao','yangguang','renbao','renshou']}" + "_2020_${1..9}," +
                "2020." + LOGICAL_TABLE + "_${['pingan','taibao','yangguang','renbao','renshou']}" + "_2020_${10..12}," +
                "2021." + LOGICAL_TABLE + "_${['pingan','taibao','yangguang','renbao','renshou']}" + "_2021_${1..9}," +
                "2021." + LOGICAL_TABLE + "_${['pingan','taibao','yangguang','renbao','renshou']}" + "_2021_${10..12}," +
                "2022." + LOGICAL_TABLE + "_${['pingan','taibao','yangguang','renbao','renshou']}" + "_2022_${1..9}," +
                "2022." + LOGICAL_TABLE + "_${['pingan','taibao','yangguang','renbao','renshou']}" + "_2022_${10..12}";

        TableRuleConfiguration ruleConfiguration = new TableRuleConfiguration("insurance_order", expression);

        ruleConfiguration.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration(DATABASE_SHARDING_COLUMN,new DatabaseByYearShardingAlgorithm(), new RangeDatabaseShardingAlgorithm()));
        ruleConfiguration.setTableShardingStrategyConfig(new ComplexShardingStrategyConfiguration(TABLE_SHARDING_COLUMN, new TableByCodeAndMonthShardingAlgorithm()));
        return ruleConfiguration;
    }

    private Map<String, DataSource> createDataSourceMap() {
        // key为数据源名称,后面分片算法取得就是这个,value为具体的数据源
        final HashMap<String, DataSource> shardingDataSourceMap = new HashMap<>();
        shardingDataSourceMap.put("default_dataSource", DataSourceUtil.createDataSource("com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://localhost:3306/sharding?userUnicode=ture&characterEncoding=utf8&serverTimezone=GMT%2B8",
                "root",
                "123456"));
        shardingDataSourceMap.put("2020", DataSourceUtil.createDataSource("com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://localhost:3306/2020_insurance_order?userUnicode=ture&characterEncoding=utf8&serverTimezone=GMT%2B8",
                "root",
                "123456"));
        shardingDataSourceMap.put("2021", DataSourceUtil.createDataSource("com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://localhost:3306/2021_insurance_order?userUnicode=ture&characterEncoding=utf8&serverTimezone=GMT%2B8",
                "root",
                "123456"));
        shardingDataSourceMap.put("2022", DataSourceUtil.createDataSource("com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://localhost:3306/2022_insurance_order?userUnicode=ture&characterEncoding=utf8&serverTimezone=GMT%2B8",
                "root",
                "123456"));
        return shardingDataSourceMap;
    }

}

DatabaseByYearShardingAlgorithm,RangeDatabaseShardingAlgorithm为按年分库的分片算法实现类,这里采用的是shardingsphere的StandardShardingStrategyConfiguration标准分片策略

PreciseShardingAlgorithm精确分片算法

**
 * @desc 按年分库精确分片算法
 */
public class DatabaseByYearShardingAlgorithm implements PreciseShardingAlgorithm<Date> {
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Date> preciseShardingValue) {
        Date createTime = preciseShardingValue.getValue();
        int year = DateUtil.year(createTime);
        for (String database : collection) {
            if (Objects.equals(database,String.valueOf(year))) {
                return database;
            }
        }
        throw new UnsupportedOperationException();
    }
}

RangeShardingAlgorithm范围分片算法


/**
 * @Description: 数据库范围分片算法
 */
public class RangeDatabaseShardingAlgorithm implements RangeShardingAlgorithm<Date> {

    /**
     * 范围分片算法
     *
     * @param availableTargetNames 所有配置的库列表
     * @param rangeShardingValue   分片值,也就是createTime的值,范围分片算法必须提供开始时间和结束时间
     * @return 所匹配库的结果
     */
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> rangeShardingValue) {
        final ArrayList<String> result = new ArrayList<>();
        Range<Date> range = rangeShardingValue.getValueRange();
        Date startTime = range.lowerEndpoint();
        Date endTime = range.upperEndpoint();

        // 起始年和结束年
        int startYear = DateUtil.year(startTime);
        int endYear = DateUtil.year(endTime);

        Collection<String> databaseList =  startYear == endYear ? theSameYear(String.valueOf(startYear), availableTargetNames, result) : differentYear(startYear, endYear, availableTargetNames, result);
        return databaseList;
    }

    // 起始年跟结束年在同一年,只需要查一个数据库
    private Collection<String> theSameYear(String startTime, Collection<String> availableTargetNames, ArrayList<String> result) {
        for (String availableTargetName : availableTargetNames) {
            if (availableTargetName.endsWith(startTime)) {
                result.add(availableTargetName);
            }
        }
        return result;
    }

    // 跨年,查多个数据库
    private Collection<String> differentYear(int startYear, int endYear, Collection<String> availableTargetNames, ArrayList<String> result) {
        for (String availableTargetName : availableTargetNames) {
            for (int i = startYear; i <= endYear; i++) {
                if (availableTargetName.endsWith(String.valueOf(i))) {
                    result.add(availableTargetName);
                }
            }
        }
        return result;
    }
}

TableByCodeAndMonthShardingAlgorithm是通过insurCompanyCode和createTime两个字段的复合分片键进行分表的分片算法实现类,这里采用的是shardingsphere的ComplexShardingStrategyConfiguration复合分片算法策略

ComplexKeysShardingAlgorithm复合分片算法


public class TableByCodeAndMonthShardingAlgorithm implements ComplexKeysShardingAlgorithm {

    @Override
    public Collection<String> doSharding(Collection availableTargetNames, ComplexKeysShardingValue complexKeysShardingValue) {
        Map<String, Collection> columnNameAndShardingValuesMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap();
        ArrayList<String> monthList = new ArrayList<>();
        ArrayList<String> codeList = new ArrayList<>();
        ArrayList<String> columnNameAndShardingList = new ArrayList<>();

        ArrayList<String> tables = new ArrayList<>();

        //精确分片
        if (CollectionUtil.isNotEmpty(columnNameAndShardingValuesMap)) {
            Collection<Date> dateCollection = (Collection) columnNameAndShardingValuesMap.get("createtime");
            if (CollectionUtil.isEmpty(dateCollection)) {
                dateCollection = (Collection) columnNameAndShardingValuesMap.get("createTime");
            }
            if (CollectionUtil.isNotEmpty(dateCollection)) {
                for (Date date : dateCollection) {
                    monthList.add(DateUtil.year(date) + "_" + (DateUtil.month(date) + 1));
                }
            }

            Collection<String> insurCompanyCodeList = (Collection) columnNameAndShardingValuesMap.get("insurcompanycode");
            if (CollectionUtil.isEmpty(insurCompanyCodeList)) {
                insurCompanyCodeList = (Collection) columnNameAndShardingValuesMap.get("insurCompanyCode");
            }
            //再通过业务code过滤
            if (CollectionUtil.isNotEmpty(insurCompanyCodeList)) {
                for (String code : insurCompanyCodeList) {
                    codeList.add(code);
                }
            }

            if (CollectionUtil.isNotEmpty(codeList) && CollectionUtil.isNotEmpty(monthList)) {
                for (String code : codeList) {
                    for (String month : monthList) {
                        columnNameAndShardingList.add(code + "_" + month);
                    }
                }
            } else if (CollectionUtil.isNotEmpty(codeList)) {

                for (String code : codeList) {
                    columnNameAndShardingList.add(code);
                }
            } else if (CollectionUtil.isNotEmpty(monthList)) {
                for (String month : monthList) {
                    columnNameAndShardingList.add(month);
                }
            } else {
                logger.error("未按分片键查询,不支持此类操作");
                throw new UnsupportedOperationException();
            }

            if (CollectionUtil.isNotEmpty(columnNameAndShardingList)) {
                for (Object tableName : availableTargetNames) {
                    if (tableName == null) {
                        continue;
                    }
                    for (String target : columnNameAndShardingList) {
                        String lowerCase = target.toLowerCase();
                        if (tableName.toString().contains(lowerCase)) {
                            tables.add(tableName.toString());
                            break;
                        }
                    }
                }
            }
        }

        Map<String, Range<Date>> columnNameAndRangeValuesMap = complexKeysShardingValue.getColumnNameAndRangeValuesMap();
        if (CollectionUtil.isNotEmpty(columnNameAndRangeValuesMap)) {
            Range<Date> dateRange = columnNameAndRangeValuesMap.get("createTime");
            Date start = dateRange.lowerEndpoint();
            Date end = dateRange.upperEndpoint();
            int startYear = DateUtil.year(start);
            int endYear = DateUtil.year(end);

            int startMonth = DateUtil.month(start) + 1;
            int endMonth = DateUtil.month(end) + 1;

            if (startYear > endYear) {
                throw new UnsupportedOperationException();
            }
            List<String> finalTables = null;
            //通过精确分片过滤的数据库表集合不为空
            if (CollectionUtil.isNotEmpty(tables)) {
                finalTables = getResults(tables, startYear, endYear, startMonth, endMonth);
            } else {
                for (Object targetName : availableTargetNames) {
                    if (targetName == null) {
                        continue;
                    }
                    tables.add(targetName.toString());
                }
                //无精确分片过滤的表集合
                finalTables = getResults(tables, startYear, endYear, startMonth, endMonth);
            }
            return finalTables;
        }
        return tables;
    }

    private List<String> getResults(ArrayList<String> tables, int startYear, int endYear, int startMonth, int endMonth) {
        List<String> finalTables = new ArrayList<>();
        if (startYear == endYear) {
            //同年
            for (String tableName : tables) {
                for (int i = startMonth; i <= endMonth; i++) {
                    if (tableName.endsWith("_" + i)) {
                        finalTables.add(tableName);
                        break;
                    }
                }
            }
            return finalTables;
        } else if ((endYear - startYear) == 1) {
            //跨一年
            String tableNameOne = tables.get(0);
            if (tableNameOne.contains(startYear + "")) {
                for (String tableName : tables) {
                    for (int i = startMonth; i <= 12; i++) {
                        if (tableName.endsWith("_" + i)) {
                            finalTables.add(tableName);
                            break;
                        }
                    }
                }
                return finalTables;
            } else if (tableNameOne.contains(endYear + "")) {
                for (String tableName : tables) {
                    for (int i = 1; i <= endMonth; i++) {
                        if (tableName.endsWith("_" + i)) {
                            finalTables.add(tableName);
                            break;
                        }
                    }
                }
                return finalTables;
            }
        } else {
            //跨两年及以上
            logger.error("跨两年及以上的多数据库查询,影响性能,暂不支持此操作");
            throw new UnsupportedOperationException();
        }
        return null;
    }
}

最终效果:

 

由于shardingsphere不能自动数据库和表,在使用前,必须先创建好数据库,然后写个测试类,写入建表脚本,这里就可以根据你的规则创建好相应的物理表了,然后新增一些测试数据,可以看到数据会按照你相应的规则插入到指定的物理表上啦,这样按照不通维度的分库分表,能减少落到每张表的数据大大减少,查询的时候一定要注意,查询条件一定要带上你分表的分片键,这样才能路由到指定的表,缩小查询范围,提高查询效率。

更多的使用技能请移步官网:概览 :: ShardingSphere

如有不妥,望各路大神批评指正。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Mycat中实现动态按年分表,你需要进行以下步骤: 1. 首先,下载Mycat并进行安装配置。你可以从官方网站http://www.mycat.org.cn/ 下载最新版本的Mycat安装包。 2. 在Mycat的配置文件中,你需要添加一个创建表的配置语句。通过修改tableNames参数,可以实现按年动态创建分表。具体的配置语句如下所示: ``` mycat:createTable{ "schemaName":"mycatdb", "shardingTable":{ "createTableSQL":"CREATE TABLE `mycatdb`.`test` (`name` varchar(10),`create_time` varchar(30));", "function":{ "clazz":"io.mycat.router.mycat1xfunction.PartitionByMonth", "properties":{ "beginDate":"2021-01-01 00:00:00", "dateFormat":"yyyy-MM-dd hh:mm:ss", "endDate":"", "columnName":"create_time" }, "ranges":{} }, "partition":{ "schemaNames":"1cloud", "tableNames":"test_$202101-202104", "targetNames":"prototype" } }, "tableName":"test" } ``` 在上述配置中,你需要修改schemaName为你的数据库名称,createTableSQL为你要创建的表的SQL语句,tableNames为按年动态生成的表名格式,targetNames为最终生成的表名前缀。 3. 配置完成后,启动Mycat服务器。你可以按照中的指导进行启动。 通过以上步骤,你就可以在Mycat中实现动态按年分表了。每个的数据将自动分配到对应的表中,方便数据管理和查询。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [MyCat2按分表操作](https://blog.csdn.net/Swlymbcty/article/details/125048968)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [mycat单数据库按日分表,按分表](https://blog.csdn.net/qq_42795685/article/details/106768430)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值