分库分表有垂直和水平两种分法。

垂直拆分比较简单,也就是本来一个数据库,数据量大之后,从业务角度进行拆分多个库。如下图,独立的拆分出订单库和用户库
image-1649859869958

水平拆分是同一个业务数据量大之后,进行水平拆分。如果订单数据达到了4000万,我们知道mysql单表存储量推荐是百万级,如果不进行处理,mysql单表数据太大,会导致性能下降。把4000万数据拆分4张表或者更多。当然也可以分库,再分表;把压力从数据库层级分开。
image-1649859875619

1.分库分表方案

1.1 hash取模方案

image-1649859885760
在我们设计系统之前,可以先预估一下大概这几年的订单量,如:4000万。每张表我们可以容纳1000万,也我们可以设计4张表进行存储。

订单数据可以均匀的放到那4张表中,这样此订单进行操作时,就不会有热点问题。但是将来的数据迁移和扩容,会很难,重新hash之后所有的数据都需要做迁移。

这里热点的意思:排除某个用户某一个时间大量写入,后面会考虑。正常情况下对订单进行操作集中到1个表中,其他表的操作很少。
订单有个特点就是时间属性,一般用户操作订单数据,都会集中到这段时间产生的订单。如果这段时间产生的订单 都在同一张订单表中,那就会形成热点,那张表的压力会比较大。

最少迁移办法可以考虑一致性hash,具体请参考文章《一致性哈希算法》

1.2 range范围方案

range方案也就是以范围进行拆分数据。
image-1649860167331

range方案比较简单,就是把一定范围内的订单,存放到一个表中;如上图id=12放到0表中,id=1300万的放到1表中。设计这个方案时就是前期把表的范围设计好。通过id进行路由存放。

  • 优点:
    此方案有利于将来的扩容,不需要做数据迁移。即时再增加4张表,之前的4张表的范围不需要改变,id=12的还是在0表,id=1300万的还是在1表,新增的4张表他们的范围肯定是 大于 4000万之后的范围划分的。

  • 缺点:
    有热点问题,因为id的值会一直递增变大,那这段时间的订单会一直在某一张表中,如id=1000万 ~ id=2000万之间,这段时间产生的订单是不是都会集中到此张表中,这个就导致1表过热,压力过大,而其他的表没有什么压力。

2.结合两者的设计

我们考虑一下数据的扩容代表着,路由key(如id)的值变大了,这个是一定的,那我们先保证数据变大的时候,首先用range方案让数据落地到一个范围里面。这样以后id再变大,那以前的数据是不需要迁移的。

但又要考虑到数据均匀,那是不是可以在一定的范围内数据均匀的呢?因为我们每次的扩容肯定会事先设计好这次扩容的范围大小,我们只要保证这次的范围内的数据均匀是不是就ok了。

2.1方案设计

我们先定义一个group组概念,这组里面包含了一些分库以及分表,如下图
image-1649860424585

上图有几个关键点:

  1. id=0~4000万肯定落到group01组中
  2. group01组有3个DB,那一个id如何路由到哪个DB?
  3. 根据hash取模定位DB,那模数为多少?模数要为所有此group组DB中的表数,上图总表数为10。为什么要去表的总数?而不是DB总数3呢?
  4. 如id=12,id%10=2;那值为2,落到哪个DB库呢?这是设计是前期设定好的,那怎么设定的呢?
  5. 一旦设计定位哪个DB后,就需要确定落到DB中的哪张表呢?

2.2核心主流程

image-1649860482715

按照上面的流程,我们就可以根据此规则,定位一个id,我们看看有没有避免热点问题。

我们看一下,id在【0,1000万】范围内的,根据上面的流程设计,1000万以内的id都均匀的分配到DB_0,DB_1,DB_2三个数据库中的Table_0表中,为什么可以均匀,因为我们用了hash的方案,对10进行取模。

在我们安排服务器时,有些服务器的性能高,存储高,就可以安排多存放些数据,有些性能低的就少放点数据。如果我们取模是按照DB总数3,进行取模,那就代表着【0,4000万】的数据是平均分配到3个DB中的,那就不能够实现按照服务器能力适当分配了。

按照Table总数10就能够达到,看如何达到:
image-1649860541290

上图中我们对10进行取模,如果值为【0,1,2,3】就路由到DB_0,【4,5,6】路由到DB_1,【7,8,9】路由到DB_2。现在小伙伴们有没有理解,这样的设计就可以把多一点的数据放到DB_0中,其他2个DB数据量就可以少一点。DB_0承担了4/10的数据量,DB_1承担了3/10的数据量,DB_2也承担了3/10的数据量。整个Group01承担了【0,4000万】的数据量。

2.3如何扩容

其实上面设计思路理解了,扩容就已经出来了;那就是扩容的时候再设计一个group02组,定义好此group的数据范围就ok了。
image-1649860589406

因为是新增的一个group01组,所以就没有什么数据迁移概念,完全是新增的group组,而且这个group组照样就防止了热点,也就是【4000万,5500万】的数据,都均匀分配到三个DB的table_0表中,【5500万~7000万】数据均匀分配到table_1表中。

2.4系统设计

思路确定了,设计是比较简单的,就3张表,把group,DB,table之间建立好关联关系就行了
group和DB的关系

table和db的关系

上面的表关联其实是比较简单的,只要原理思路理顺了,就ok了。在开发的时候不要每次都去查询三张关联表,可以保存到缓存中(本地jvm缓存),这样不会影响性能

3.思考

这样的操作是把订单id做了分库分表。如果我需要查询某个用户下的所有订单,这样分表之后,会导致查询变得复杂。

这样的话是不是可以考虑在做hash的时候,使用用户id进行一致性hash,使用一致性hash方便日后进行数据库扩容。同时,创建一个表用于保存用户在每一个组里的最大的订单id,以及有多少条订单。查询时根据这个订单id查到用户订单在哪个组里,然后按照用户id做hash后找到对应的db,然后再通过最大id查找表,然后查数据。如果计算在当前组查询的总数比订单数多,则自动查询上一个组。

参考

如何永不迁移数据和避免热点? 根据服务器指标分配数据量(揭秘篇)
你知道怎么分库分表吗?如何做到永不迁移数据和避免热点吗?