美文网首页
分库分表

分库分表

作者: 技术灭霸 | 来源:发表于2021-11-10 10:33 被阅读0次

    其实就是字面意思,很好理解:

    • 分库:从单个数据库拆分成多个数据库的过程,将数据散落在多个数据库中。
    • 分表:从单张表拆分成多张表的过程,将数据散落在多张表内。

    2 为什么要分库分表?

    关键字:提升性能、增加可用性。

    从性能上看

    随着单库中的数据量越来越大、数据库的查询QPS越来越高,相应的,对数据库的读写所需要的时间也越来越多。数据库的读写性能可能会成为业务发展的瓶颈。对应的,就需要做数据库性能方面的优化。本文中我们只讨论数据库层面的优化,不讨论缓存等应用层优化的手段。

    如果数据库的查询QPS过高,就需要考虑拆库,通过分库来分担单个数据库的连接压力。比如,如果查询QPS为3500,假设单库可以支撑1000个连接数的话,那么就可以考虑拆分成4个库,来分散查询连接压力。

    如果单表数据量过大,当数据量超过一定量级后,无论是对于数据查询还是数据更新,在经过索引优化等纯数据库层面的传统优化手段之后,还是可能存在性能问题。这是量变产生了质变,这时候就需要去换个思路来解决问题,比如:从数据生产源头、数据处理源头来解决问题,既然数据量很大,那我们就来个分而治之,化整为零。这就产生了分表,把数据按照一定的规则拆分成多张表,来解决单表环境下无法解决的存取性能问题。

    从可用性上看

    单个数据库如果发生意外,很可能会丢失所有数据。尤其是云时代,很多数据库都跑在虚拟机上,如果虚拟机/宿主机发生意外,则可能造成无法挽回的损失。因此,除了传统的 Master-Slave、Master-Master 等部署层面解决可靠性问题外,我们也可以考虑从数据拆分层面解决此问题。

    此处我们以数据库宕机为例:

    • 单库部署情况下,如果数据库宕机,那么故障影响就是100%,而且恢复可能耗时很长。
    • 如果我们拆分成2个库,分别部署在不同的机器上,此时其中1个库宕机,那么故障影响就是50%,还有50%的数据可以继续服务。
    • 如果我们拆分成4个库,分别部署在不同的机器上,此时其中1个库宕机,那么故障影响就是25%,还有75%的数据可以继续服务,恢复耗时也会很短。

    当然,我们也不能无限制的拆库,这也是牺牲存储资源来提升性能、可用性的方式,毕竟资源总是有限的。

    二 如何分库分表

    1 分库?分表?还是既分库又分表?

    从第一部分了解到的信息来看,分库分表方案可以分为下面3种:


    image.png

    分库与分表路由策略

    用户ID进行取模操作,如果用户ID为UUID请先hash然后在进行取模。


    1、中间变量 = user_id%(库数量*每个库的表数量);
    2、库序号 = 取整(中间变量/每个库的表数量);
    3、表序号 = 中间变量%每个库的表数量;
    
    1、中间变量 = 262145%(256*1024)= 1;
    2、库序号 = 取整(1/1024)= 0;
    3、表序号 = 1%1024 = 1;
    

    hash路由策略的优缺点,优点是:数据分布均匀;缺点是:数据迁移的时候麻烦,不能按照机器性能分摊数据。

    分库分表方案

    分库分表主要有垂直切分和水平水平,这次针对的问题是单表容量达到瓶颈的问题,因此采用水平切分。

    以下把这张表叫做 t_file 表吧。

    切分策略

    水平切分策略主要有hash切分和范围切分,优缺点如下表。


    最终经过考虑,决定采用范围切分策略,主要原因是 t_file 的主键原来是整型的自增主键,不适合用hash切分。目前数据量非常大,并且还涉及到另外几张表,有些表存储了一个列表,列表里存的就是整型ID,那些表也非常大,改的话影响范围太大,所以不可能把整型修改成其它类型。

    不分库,只分表

    这张表我们有没有必要分库呢?我和我的mentor讨论了一下,觉得是暂时只做分表即可。只要原因有以下几个:

    1. 我们有主从,做了读写分离(也不算完全读写分离,个别对延时方面要求高的SQL读的是主库),并且跟DBA大佬讨论过,数据库压力并没有那么大。而且我们如果在主从上又做分库,那每个实例启动后都需要连接并且管理大量的数据源,需要消耗大量资源,不是很值得
    2. 这张表,是存在一个独立的库中的,那个库只有这一张表!由于这张表数据里较大,之前我们就把这张表分出来放到一个独立的库中了,它跟其它表并不是在同一个数据库。所以它压力更小了
    3. 由于是水平切分,我们后期要是再想做分库的话,不用再次迁移数据,或者说迁移数据的复杂度很低,并且路由规则也不需要改太多。以后我们想做分库再做就是了,现在不用一步到位

    技术选型

    目前比较主流的分库分表中间件有mycat、sharding-jdbc、tidb,最终决定使用sharding-jdbc,原因有下:

    1. 现在只打算分表,而不分库,Mycat和tidb并没有太大优势
    2. mycat是proxy层解决方案,需要部署、维护,并且本身是单点的,高可用性差,如果想要集群还需要再引入别的中间件
    3. tidb资源利用率低、维护成本也很高

    分布式ID实现

    不重复的ID该怎么实现?
    分库分表后,需要保证数据插入后ID的唯一性,实现不重复的ID一般有以下几种方案:

    • 使用数据库的自增ID。该表的数据比较特殊,只会新增,不会删除,并且极少有修改操作,因此可以考虑继续使用数据库的自增ID。而要保证每个表的ID都不重复,那就要先确定好每个表的ID范围没有交集,再在建表时设置好表的起始自增序列号,业务方需要确保在插入数据时判断该表的最大ID必须小于下一个表的起始自增序列号。例如我们打算每个表存储100行数据,那么 t_file_0 表的ID范围为 0 ~ 99(当然,我们没有ID为0的数据,最小的是1),t_file_1 表的ID范围为 100 ~ 199 ,t_file_2 表的ID范围为 200 ~ 299……

    使用方案四,就会有三个问题:

    1. 建表前需要确认好每个表的ID范围,建表时设置好起始自增序列号
    2. 在插入ID时维护好当前要插入的数据对应的表下标,根据上面的例子,当 t_file 里的数据最大ID为99时,后面的数据不能再插入到该表中,而是要插入到 t_file_1 中,这意味着我们需要维护一个当前数据对应的表下标,即“我需要知道现在的数据需要插入到哪个表”
    3. 使用自己的策略就意味着不能使用sharding-jdbc的ID生成策略了

    预留ID + 不依赖redis

    预留ID数 > 实例数 * 每个实例的最大线程数 * 100

    我可以让每张表都预留一部分ID,这部分ID可以插入一部分数据,但是插入之后,以后的数据就插入到下一张表。只要我们预留的ID范围足够大,那在高并发场景下就不会出现ID超范围的情况。

    比如** t_file_0 的预留ID范围是 9000~9999,当插入一批数据后得到的最大ID是9100,达到了预留ID范围,那就更新表下标**,以后的数据插入到 t_file_1 中。即使多个线程同时插入一大批数据,只要我们预留的范围足够大,就不会出现最大ID超过这张表范围的情况。这样就不用手动控制事务了。


    相关文章

      网友评论

          本文标题:分库分表

          本文链接:https://www.haomeiwen.com/subject/xyhjzltx.html