前言:一旦遇到数据量比较大的时候,小伙伴应该都知道要对数据进行拆分。有垂直和水平两种。
垂直区分
比较简单,也就是本来一个数据库,数据量大之后,从业务角度进行拆分多个库。如下图,独立的拆分出订单库和用户库。
水平拆分
的概念,是同一个业务数据量大之后,进行水平拆分。
上图中订单数据达到了4000万, 大家都知道Mysq单表存储量推荐是
百万级
。大量的数据囤积,如果不进行处理,则会造成性能拖慢。这个时候我们就要对数据进行水平拆分,把数据拆分成4张或者更多张表。当然也可以分库,再分表。把压力从数据库层级分开。
一、谈谈分库分表方案
分库分表中常用的方案:Hash取模方案
和range范围方案
;
路由算法
为最主要的算法,指得是把路由的Key按照指定的算法进行存放
;
以下是两个方案的特点
:
1.1、Hash取模方案
在我们设计之时,需要先预估大概这几年的订单量,如:4000W。每张表我们可以容纳1000W,我们就可以设计4张表进行存储。
那具体
路由
是如何存储的呢?hash取模方案
就是对指定的路由key(如:id)对分表的总数
进行取模。
上图中,id=12的订单,对4进行取模,得到0,那此订单会放到0表中。
id=13的订单,取模得到为1,就会放到1表中。
为什么对4取模,是因为分表总数是
4。
-
优点
:订单数据可以均匀的放在4张表中,在订单操作的时候,就不会出现热点问题
。
什么是
热点
?
订单集中操作在一张表中,而其他表的操作却很少。
订单有个特点就是时间属性,一般用户操作订单数据的时候。都会集中在这个时间段产生的订单。如果这段时间产生的订单都在同一个表的情况下,就会形成热点问题
,此时的压力就特别大。
-
缺点
:数量量的增大,就非常有必要做数据迁移、扩容,这将非常的困难。
例如
:业绩突飞猛进,订单量超出了4000万的量,需要增加分表数
。如果我们增加4个表
问题就来了
,一旦我们增加了分表总数,取模的计数就变成8,id=12的订单按照此方案就会到4表中查询,但之前的查询是在0表中,这就导致了数据查不到,就是因为取模的基数产生了变化。
遇到这种情况这么办?可能大家想的就是数据迁移。把之前4000W的数据,重新做一个Hash取模方案,放到新的规划分表中。有些小公司可以接受晚上停机迁移,但大公司是不允许停机做数据迁移的。这其实是个非常痛苦的事情,那有没有不需要做数据迁移的方案呢,我们看下面的方案
1.2、range范围方案
range范围方案
就是以指定的范围进行拆分数据。
此方案就是把一定范围内的订单,存放到一个表中。如上图,id=12存放到0表中,id=1300万的存放到1表中。前期设计就是把表的范围设定好,通过id进行路由存放。
-
优点
:利于扩容,不需要做数据迁移。即使再增加4张表,之前的4张表也不用改变。且新增的4张表肯定大于4000万之后的范围划分。
-缺点
:会有热点问题
。因为id会一直递增变大,那就是说,会有一个时间段的订单集中在一个表中,例如id=1000W~2000W,集中在这个表中的订单就会导致1表过热,压力增大,而其他的表就没有什么压力。
总结:
hash取模方案
:没有热点问题,但扩容迁移数据痛苦
range方案
:不需要迁移数据,但有热点问题。
那么问题来了,能否做到即不需要迁移数据,又能解决数据热点的问题呢
?
其实还有一个现实需求,能否根据服务器的性能以及存储高低,适当均匀调整存储呢?
二、方案思路
在Range方案
的基础上做Hash取模
。
先使用Range方案让数据落在一个范围里,这样以后id不论怎么变大,以前的数据都不需要迁移。
而后进行数据均分
,那么是不是可以在一定范围内进行数据均分
。
未来需要扩容之时,就可以提前规划好扩容的范围大小,保证范围内的数据均分。
实现即不需要迁移数据,又能解决数据热点的问题
。
三、方案设计
由上图可知,我们定义了一个Group组概念,将DB0、DB1、DB2三个数据库纳入Group01组。
DB0~DB2中,也根据range范围方案
取好了表与表之间id存放的范围。
当id=0~4000万,数据落入group01组。
关键问题
1. gruop01有3个DB,我们如何精准分配路由id到哪个DB?
2. 根据
hash取模定位
DB,那模数为多少?这里的模数
必须取所有此group组DB中的表数。上图总表数为10
。为什么要取表的总数?而不是DB总数3呢?3. 如id=12,id%10=2;那值为2,落到哪个DB库呢?这是前期设定好的,那怎么设定的呢?
4. 一旦设计定位哪个DB后,就需要确定落到DB中的哪张表呢?
四、核心主流程
根据上图的流程,我们可以有效的避免热点问题
。
可以来看一下,id在【0,1000w】范围内的,根据上面的流程设计。
1000万以内的id都均匀的分配到DB0、DB1、DB2三个数据表中的table_0中,就是用到了Hash取模方案
,对10
进行取模。
那为什么对表的总数10取模,而不是DB的总数3进行取模?我们看一下为什么DB_0是4张表,其他两个DB_1是3张表?
在我们安排服务器时,有些服务器的性能存储比较高,就可以安排多存放些数据,有些性能低的就少放点数据。如果我们取模是按照DB总数3,进行取模,那就代表着【0,4000万】的数据是平均分配到3个DB中的,那就不能够实现按照服务器能力适当分配了。
而按照Table总数10就能够达到,如下图:
上图中我们对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万】的数据量。
上面的知识笔录,基本可以解决热点的问题,以及可以按照服务器指标,设计数据量的分配。
五、如何扩容
其实上面设计思路理解了,扩容就已经出来了;那就是扩容的时候再设计一个group02组,定义好此group的数据范围就ok。
因为是新增的一个group02组,所以就没有什么数据迁移概念,完全是新增的group组,而且这个group组照样就防止了热点,也就是【4000万,5500万】的数据,都均匀分配到三个DB的table_0表中,【5500万~7000万】数据均匀分配到table_1表中等。
六、系统设计
思路确定了,设计是比较简单的,就3张表,把group,DB,table之间建立好关联关系就行。
group和DB的关系
table和db的关系
上面的表关联其实是比较简单的,只要原理思路理顺了,就ok了。小伙伴们在开发的时候不要每次都去查询三张关联表,可以保存到缓存中(Redis缓存),这样不会影响性能。
纯学习记录,望能够帮助大家。大佬勿喷~