前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分库分表下,扩容数据免迁移方案

分库分表下,扩容数据免迁移方案

原创
作者头像
Joseph_青椒
发布2023-08-04 20:25:22
5910
发布2023-08-04 20:25:22
举报
文章被收录于专栏:java_josephjava_joseph

这篇专门来谈谈二次扩容,数据迁移问题,也就是上一个文章抛出的问题

分库分表初探-腾讯云开发者社区-腾讯云 (tencent.com)

需求

1、数据量增加,扩容避免迁移数据或者免迁移

2、前期数据量不多,不浪费库表系统资源

项目背景

短链平台,就是我们手机收到的短信,这里分库分表篇章,有必要结合场景来更好的理解。

添加描述

通过这个图,就大概可以理解业务需求了,短链平台就是将商家的长链转换为短链,商家决定向哪个平台投放广告,我们平台做出一个

pv,uv浏览量,点击量,商家就知道他在哪个平台的ROI比较高,同样,投放的费用是可降低的,长链接投放广告,和短链接,肯定是不一样的。

好,为何短链需要分库分表,来看下短链的生成算法,

短链采用的是murmurHash算法,他是一种非加密的 hash算法,它生成的是10进制的数字,我们转化为62进制,的话,会有6位的码,62进制,为啥不用64进制,1是64进制有特殊符号,2是62进制可以让码更短一些,

为啥用murmurhash,不用自增id再转62进制?

这就涉及到业务安全的问题了

设计一个短链码,让别人进行访问,需要做到的就是不重复,和业务安全

下面说另外两个方案,看看为啥不行,1自增id转62进制,2对长链接直接进行MD5加密

第二个方案,是有损压缩,数据量越大,冲突的概率会越大,这样是不行

第一个方案,自增id,这样是由数据安全问题的,自增id这个是不可乱用的,别人可以通过62进制转换10进制这样,获取用户数量,业务安全就出现了问题,是不行的

再者,其实大部分数据库,分库分表的情况下,我们采用的分布式id,雪花算法,这个自增是和业务没关系的,是安全的,但是当我们不需要分库分表的时候,单个带有自增id的表,就可以将主键的业务使用废弃,新增一个biz_id,业务id,来进行数据安全的维护

murmurhash3,32位,能产生43亿的短链!,切效率是很高的

好,这就说明了为何用murmurhash做为短链码来使用了

这里先不做具体的实现,我们在解决数据迁移问题方案的时候,自然会讲

数据量

再讲下数据量,超理想的情况下,首年日活10w,首年日新增短链就是10w*50=500w,年新增短链也就是18亿

三年,预估就是100亿,

murmurhash转62进制,43亿,43亿,好像不太够

拼上库表位,创造的短链能达到万亿级别,增加一位就是×62

万亿级别,再加上后期的归档,基本能满足需求了

刚才说的库表位,是什么,听我讲,下面的,

数据迁移问题

先看一个简单的方案

通过短链码hash取模,的方式路由,300亿,我们整16个库,每个库64个表,

分片键partionKey就是短链码

那么分片算法,

库ID = 短链码hash值%库数量

表ID = 短链码hash值/库数量%表数量

这样做肯定是够用的1024表,单表1000w的话,短链码都能给分完,那么这得浪费多少服务器哈哈

但是你要是分少一点,2个库,每个库4张表,那么后面业务量上来,扩容问题咋搞,很棘手

这种方案很简单,的确,但是预估数据量,是很难搞的,谁都难于估计。那么如何让前期少量服务器,且还能做后续的快速扩容或者免扩容呢???

这里提供解决方案!

数据免迁移方案–增加库表位

对,这个方案就是通过给短链码增加库表位,还是通过短链码作为分片键,但是路由规则依靠的是我们增加的库表位!

我们搞三个库,每个库2张表

对应库的名称是ds0 ds1 dsa 每个库中 short_link_0 short_linke_a‘

如何拼装库表位置?

这里先给出murmurhash的方法

代码语言:javascript
复制
/**
     * murmurHash算法
     * @param param
     * @return
     */
    public static long murmurHash32(String param){
        long murmurHash32 = Hashing.murmur3_32().hashUnencodedChars(param).padToLong();
        return murmurHash32;
    }
    
 //这是在CommonUtil下的方法,就是方便下面使用

生成短链的组件

代码语言:javascript
复制
@Component
public class ShortLinkComponent {
    private static final String CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    /**
     * 生成短链码
     * @param param
     * @return
     */
    public String createShortLinkCode(String param){
        long murmurHash32 = CommonUtil.murmurHash32(param);
        //进制转换
        String code = encodeToBase62(murmurHash32);

        String randomDBPrefix = ShardingDBConfig.getRandomDBPrefix(code);
        String randomTableSuffix = ShardingTableConfig.getRandomTableSuffix(code);

        String shortLinkCode = randomDBPrefix+code+randomTableSuffix;

        return shortLinkCode;
    }

    /**
     * 10进制转62进制
     * @param num
     * @return
     */
    private static String encodeToBase62(long num) {
        //StringBuffer:线程安全;    StringBuilder:线程不安全
        StringBuffer sb = new StringBuffer();
        do {
            int i = (int) (num % 62);
            sb.append(CHARS.charAt(i));
            num /= 62;
            // num = num/ 62;
        } while (num > 0);

        String value = sb.reverse().toString();
        return value;
    }

}

这里的逻辑就很简单了,无非就是拼装库表位,短链码code的生成就是前面说的通过murmurHash算法转62进制

我们看一下库表位随机获取的逻辑

代码语言:javascript
复制
public class ShardingDBConfig {
    /**
     * 存储数据库位置编号
     */
    private static final List<String> dbPrefixList = new ArrayList<>();
    private static Random random = new Random();

    //启用哪些库的前缀
    static {
        dbPrefixList.add("0");
        dbPrefixList.add("1");
        dbPrefixList.add("a");
    }

    /**
     * 获取随机的前缀
     * @return
     */
    public static String getRandomDBPrefix(){


        int index = random.nextInt(dbPrefixList.size());

        return dbPrefixList.get(index);
    }

}

代码语言:javascript
复制
public class ShardingTableConfig {
    /**
     * 存储数据库位置编号
     */
    private static final List<String> tableSuffixList = new ArrayList<>();
    private static Random random = new Random();

    //启用哪些表的后缀
    static {
        tableSuffixList.add("0");
        tableSuffixList.add("a");
    }

    /**
     * 获取随机的后缀
     * @return
     */
    public static String getRandomTableSuffix(){


        int index = random.nextInt(tableSuffixList.size());

        return tableSuffixList.get(index);
    }

}

这样生成的短链码就包含要路由的信息了

如何路由?

代码语言:javascript
复制
#分库分表配置
spring.shardingsphere.props.sql.show=true
spring.shardingsphere.datasource.names=ds0,ds1,dsa
#ds0配置
spring.shardingsphere.datasource.ds0.connectionTimeoutMilliseconds=30000
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.idleTimeoutMilliseconds=60000
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://[你的ip]:3306/dcloud_link_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.maintenanceIntervalMilliseconds=30000
spring.shardingsphere.datasource.ds0.maxLifetimeMilliseconds=1800000
spring.shardingsphere.datasource.ds0.maxPoolSize=50
spring.shardingsphere.datasource.ds0.minPoolSize=50
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=【你的密码】

#ds1配置
spring.shardingsphere.datasource.ds1.connectionTimeoutMilliseconds=30000
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.idleTimeoutMilliseconds=60000
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://[你的ip]:3306/dcloud_link_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.maintenanceIntervalMilliseconds=30000
spring.shardingsphere.datasource.ds1.maxLifetimeMilliseconds=1800000
spring.shardingsphere.datasource.ds1.maxPoolSize=50
spring.shardingsphere.datasource.ds1.minPoolSize=50
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=【你的密码】


#dsa配置
spring.shardingsphere.datasource.dsa.connectionTimeoutMilliseconds=30000
spring.shardingsphere.datasource.dsa.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.dsa.idleTimeoutMilliseconds=60000
spring.shardingsphere.datasource.dsa.jdbc-url=jdbc:mysql://[你的ip]:3306/dcloud_link_a?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.dsa.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.dsa.maintenanceIntervalMilliseconds=30000
spring.shardingsphere.datasource.dsa.maxLifetimeMilliseconds=1800000
spring.shardingsphere.datasource.dsa.maxPoolSize=50
spring.shardingsphere.datasource.dsa.minPoolSize=50
spring.shardingsphere.datasource.dsa.username=root
spring.shardingsphere.datasource.dsa.password=【你的密码】

这只是数据库的配置,

我们看一下数据源,以及分片算法怎么配置

代码语言:javascript
复制
#----------短链,策略:分库+分表--------------
# 先进行水平分库,然后再水平分表
spring.shardingsphere.sharding.tables.short_link.database-strategy.standard.sharding-column=code
spring.shardingsphere.sharding.tables.short_link.database-strategy.standard.precise-algorithm-class-name=net.joseph.strategy.CustomDBPreciseShardingAlgorithm


# 水平分表策略,自定义策略。   真实库.逻辑表
spring.shardingsphere.sharding.tables.short_link.actual-data-nodes=ds0.short_link,ds1.short_link,dsa.short_link
spring.shardingsphere.sharding.tables.short_link.table-strategy.standard.sharding-column=code
spring.shardingsphere.sharding.tables.short_link.table-strategy.standard.precise-algorithm-class-name=net.joseph.strategy.CustomTablePreciseShardingAlgorithm

水平分库这里,采用的是,自定义的分片算法,分库不需要指定数据源,我们看一下如何通过标准分片算法,将短链码的库位提出来,并通过这个进行分片的

这个分片算法是这样定义的

代码语言:javascript
复制
public class CustomDBPreciseShardingAlgorithm implements PreciseShardingAlgorithm<String> {
    /**
     * @param availableTargetNames 数据源集合
     *                             在分库时值为所有分片库的集合 databaseNames
     *                             分表时为对应分片库中所有分片表的集合 tablesNames
     * @param shardingValue        分片属性,包括
     *                             logicTableName 为逻辑表,
     *                             columnName 分片健(字段),
     *                             value 为从 SQL 中解析出的分片健的值
     * @return
     */

    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {

        //获取短链码的第一位
        String codePrefix = shardingValue.getValue().substring(0,1);

        for(String targetName:availableTargetNames){

            //获取库名的最后一位
            String targetNameSuffix = targetName.substring(targetName.length()-1);
                //如果一致则返回
                if(codePrefix.equals(targetNameSuffix)){
                    return targetName;
                }

        }
        throw new BizException(BizCodeEnum.DB_ROUTE_NOT_FOUND);
    }
}

这个debug是这样的

我们就可以知道,遍历targetName获取的最后一位和短链码第一位比较,一样的话,就return,就会路由到1库

再看分表的配置

可以看到配置文件是到逻辑表的,数据节点为啥没由在配置到真实的数据节点?

这个数不需要我们在配置文件中找的,配置文件中不需要再匹配,因为再水平分片的标准分片算法中,已经做到了库的路由,直接获取

这就路由到了库中对应的表

数据倾斜问题

当表到达1000w,需要扩容的时候,我们直接在生成短链码的逻辑中,添加上就行了,

但是,要注意数据倾斜不均匀的问题,因为老库已经快到1000w数据了,我们就可以给他进行增加权重,让数据走新库更多一些。

到这里,分库分表数据免迁移方案就结篇了!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 需求
  • 项目背景
  • 数据量
  • 数据迁移问题
  • 数据免迁移方案–增加库表位
    • 如何拼装库表位置?
      • 如何路由?
        • 数据倾斜问题
        相关产品与服务
        云数据库 MySQL
        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
        http://www.vxiaotou.com