自定义分库分表设计

一、背景

随着项目的不断运行数据库中单表数据不断的在增大,已经达到了千万级别以上。此时单表数据的查询与修改都很困难,系统查询时间严重超时,客户端接口返回经常出现错误。newsql(TIDB,OCEANBASE)成本高。故选择分库分表方案,降低单表的数据量

二、方案

动态数据源 切换与sharding jdbc 相结合,实现可选择的动态切分数据表,

三、动态数据源

  • 调用AddDefineDataSource组件的addDefineDynamicDataSource()方法,获取原来targetdatasources的map,并将新的数据源信息添加到map中,并替换targetdatasources中的map

  • 切换数据源时可以使用@DataSource(value = “数据源名称”),或者DynamicDataSourceContextHolder.setContextKey(“数据源名称”)

package com.dwl.datasource.config;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.Map;

/**
 * 动态数据源
 * 调用AddDefineDataSource组件的addDefineDynamicDataSource()方法,获取原来targetdatasources的map,并将新的数据源信息添加到map中,并替换targetdatasources中的map
 * 切换数据源时可以使用@DataSource(value = "数据源名称"),或者DynamicDataSourceContextHolder.setContextKey("数据源名称")
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DynamicDataSource extends AbstractRoutingDataSource {
    //备份所有数据源信息,
    private Map<Object, Object> defineTargetDataSources;

    /**
     * 决定当前线程使用哪个数据源
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDynamicDataSourceKey();
    }
}

package com.dwl.datasource.config;

import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component;

/**

  • 数据源切换处理

  • @author dongwei */ @Slf4j public class DynamicDataSourceHolder { ////

    • 保存动态数据源名称 */ private static final ThreadLocal DYNAMIC_DATASOURCE_KEY = new ThreadLocal<>();

    • 设置/切换数据源,决定当前线程使用哪个数据源 */ public static void setDynamicDataSourceKey(String key){ log.info(“数据源切换为:{}”,key); DYNAMIC_DATASOURCE_KEY.set(key); }

    • 获取动态数据源名称,默认使用mater数据源 */ public static String getDynamicDataSourceKey(){ String key = DYNAMIC_DATASOURCE_KEY.get(); return key == null ? “default” : key; }

    • 移除当前数据源 */ public static void removeDynamicDataSourceKey(){ log.info(“移除数据源:{}”,DYNAMIC_DATASOURCE_KEY.get()); DYNAMIC_DATASOURCE_KEY.remove(); } }

动态注册sharding数据源

将 ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, properties);生成的数据源注入到DynamicDataSource dynamicDataSource中,利用spring aop管理数据源,使得利用注解可以切换数据源

三、分表策略

1、建表

create table sharding.sharding_data_source
(
    id                int auto_increment comment '自增主键'
        primary key,
    rule_name         varchar(255) null comment '环境前缀',
    source_type       varchar(255) null comment '数据源类型,(sharding,general)',
    database_type     varchar(255) null comment '0:Mysql  1:SQLServer',
    url               varchar(255) null comment '连接地址',
    user_name         varchar(255) null comment '数据源 用户名',
    password          varchar(255) null comment '数据源密码',
    driver_class      varchar(255) null comment '驱动全类名',
    master_slave_type varchar(255) null comment '主(master)  从(slave)',
    sharding_column   varchar(255) null comment '分库的列',
    data_node_name    varchar(255) null comment '数据节点名称'
)
    charset = utf8mb3;


create table sharding.sharding_data_source_detail
(
    id              int auto_increment comment '自增id'
        primary key,
    data_source_id  int          null comment '数据源id',
    logical_table   varchar(255) null comment '逻辑表名',
    sharding_column varchar(255) null comment '分表列',
    actual_table    varchar(255) null comment '物理表名'
)
    charset = utf8mb3;

sharding_data_source 是数据源表,通过此表中的数据来建立新的数据源 sharding.sharding_data_source_detail为具体分表策略表,将分表的字段与规则加入到此表中。当项目启动时,利用CommandLineRunner 将表中数据加载到 jvm中。

四、分表策略

项目是以租户的saas项目,结合项目实际需要,利用租户id字段 domain_id与时间自由组合分表。采用sharding jdbc 的 ComplexKeysShardingAlgorithm 分片策略,实现了组合id与其他建的 笛卡尔积式的分表。

package com.dwl.dwlshardingjdbc.config.algorithm;

import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;

import java.util.*;

public class ComplexShardingAlgorithm implements ComplexKeysShardingAlgorithm {


    @Override
    public Collection<String> doSharding(Collection collection, ComplexKeysShardingValue complexKeysShardingValue) {
        String logicTableName = complexKeysShardingValue.getLogicTableName();
        Map<String,Range> columnNameAndRangeValuesMap = complexKeysShardingValue.getColumnNameAndRangeValuesMap();
        Map<String,Collection> columnNameAndShardingValuesMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap();

        Set<String> valueKeys = columnNameAndShardingValuesMap.keySet();
        Map<String,List<String>> keyValueMap = Maps.newConcurrentMap();
        for (String valueKey : valueKeys) {
            List<String> suffixList = keyValueMap.get(valueKey);
            if(suffixList==null){
                suffixList = Lists.newArrayList();
            }
            Collection collection1 = columnNameAndShardingValuesMap.get(valueKey);
            for (Object o : collection1) {
                if(o instanceof Date){
                    if(!suffixList.contains(DateUtil.format((Date) o, "yyyyMM"))){
                        suffixList.add(DateUtil.format((Date) o, "yyyyMM"));
                    }
                }else {
                    if(!suffixList.contains(o.toString())){
                        suffixList.add(o.toString());
                    }
                }
            }
            keyValueMap.put(valueKey,suffixList);
        }
        Set<String> rangeKeyStrings = columnNameAndRangeValuesMap.keySet();
        for (String rangeKeyString : rangeKeyStrings) {
            List<String> suffixList = keyValueMap.get(rangeKeyString);
            if(suffixList==null){
                suffixList = Lists.newArrayList();
            }
            Range range = columnNameAndRangeValuesMap.get(rangeKeyString);
            for (Object s : collection) {
                String autualTableName = s.toString();
                String replace = autualTableName.replace(logicTableName, "");
                //如果有上限
                if(range.hasUpperBound()){
                    Comparable comparable = range.upperEndpoint();
                    if(comparable instanceof Date){
                        comparable = DateUtil.format((Date) comparable, "yyyyMM");
                    }
                    String suffix = comparable.toString();
                    for (String s1 : replace.split("_")) {
                        if(suffix.length() == s1.length() && 0 <= suffix.compareTo(s1)){
                            if(!suffixList.contains(s1)){
                                suffixList.add(s1);
                            }
                        }
                    }
                }
                //如果有下限
                if(range.hasLowerBound()){
                    Comparable comparable = range.lowerEndpoint();
                    if(comparable instanceof Date){
                        comparable = DateUtil.format((Date) comparable, "yyyyMM");
                    }
                    String suffix = comparable.toString();
                    for (String s1 : replace.split("_")) {
                        if(suffix.length() == s1.length() && 0 >= suffix.compareTo(s1)){
                            if(!suffixList.contains(s1)){
                                suffixList.add(s1);
                            }
                        }
                    }
                }
            }
            keyValueMap.put(rangeKeyString,suffixList);
        }

        // 利用笛卡尔积 求返回的结果表
        List<List<String>> tempValueList = Lists.newArrayList(keyValueMap.values());

        List<List<String>> result = Lists.cartesianProduct(tempValueList);

//        List<List<String>> result = Lists.newArrayList();
//        this.descartes(tempValueList,result,0,Lists.newArrayList());

        List<String> shardingSuffix = Lists.newArrayList();
        for (List<String> strings : result) {
            List<String> tempList = Lists.newArrayList();
            tempList.addAll(strings);
            String suffix = String.join("_",tempList);
            Collections.reverse(tempList);
            String suffixReverse = String.join("_", tempList);
            for (Object o : collection) {
                String actualTable =o.toString();
                if(actualTable.contains(suffix)|| actualTable.contains(suffixReverse)){
                    shardingSuffix.add(actualTable);
                }
            }
        }
        return shardingSuffix;
    }

    /**
     * 求笛卡尔积
     * @param dimvalue
     * @param result
     * @param layer
     * @param curList
     */
    private  void descartes(List<List<String>> dimvalue, List<List<String>> result, int layer, List<String> curList) {
        if (layer < dimvalue.size() - 1) {
            if (dimvalue.get(layer).isEmpty()) {
                descartes(dimvalue, result, layer + 1, curList);
            } else {
                for (int i = 0; i < dimvalue.get(layer).size(); i++) {
                    List<String> list = Lists.newArrayList(curList);
                    list.add(dimvalue.get(layer).get(i));
                    descartes(dimvalue, result, layer + 1, list);
                }
            }
        } else if (layer == dimvalue.size() - 1) {
            if (dimvalue.get(layer).isEmpty()) {
                result.add(curList);
            } else {
                for (int i = 0; i < dimvalue.get(layer).size(); i++) {
                    List<String> list = Lists.newArrayList(curList);
                    list.add(dimvalue.get(layer).get(i));
                    result.add(list);
                }
            }
        }
    }
}



五、事务问题

在采用里分表的方法上增加@ShardingTransactionType(TransactionType.LOCAL) @Transactional(rollbackFor = Exception.class)实现事务的管理。也可以采用分布式事务 seata

六、历史数据迁移问题

利用mybatis plus 的流查询 查询出表中所有数据,然后对数据进行一一处理

@Select("select * from ${tableName} ")
    @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
    @ResultType(Map.class)
    void getOrgWithBigData(@Param("tableName") String tableName, ResultHandler handler);

每1000条数据为一组插入到目标中去,注意最后一次不足1000条,需要格外处理。(查询出所有的记录数,与处理进度进行对比。别采用 information_schema中的记录数,不准确

七、表结构变更与索引创建问题

通过提供的接口走数据源进行sql执行,经测试会自动分库分表

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值