架构设计笔记_14_关键模式_分库分表
2021年3月 贵阳花溪
即使读写分离实施以后,主从延迟问题也解决了,慢查询问题也没有了,索引也合理的建立了,但是当单库的数据量达到一定极限后,其性能也是无法撑起来的。
对于Runnf来说,也是一款日活千万的APP,当然这个不是一蹴而就的,而是随着业务发展逐渐形成的,每天数据量的增长也是相当快的,根据当时我们实际的情况,即8核32G的数据库配置,也是各种连接超时的,因为数据库服务器的资源以及严重负载了,数据量占用的磁盘也是不小了,连接池的连接也不够了,新的连接申请也大量延时,在加上主从复制,对于主服务也是很大的一部分性能损耗。单表达到5000万条数据的时候,应该考虑分库分表。
分库分表和读写分离在数据存储上是不同的,分库分表的数据是分区的,每个节点上面存储的数据只是整个系统数据的一部分,而读写分离存储的数据,每个节点上都是全量一样的。
还是那句话,不到万不得已不要分库分表。
先从整体分析一下库里面表,那些是核心业务表,那些不是,Runnf核心就是用户和轨迹表,以及动态表,其他如活动表(和第三方合作的活动),福利表(每周一次的抽奖或者里程激励活动),都是周边业务,没有直接关系。这些周边的表首先要独立出来在单独库里面,这样可以减少对于主表所在库的压力,特别是这些非核心业务表数据量也不小时效果明显。(垂直拆分)
核心表里程表里Location已经超过3000万条了,整个数据库服务器的IO已经很高了,无法支撑业务发展,需要将Location表里数据拆分到其他的库和表里面,有几种方式可以选择,不过一般在选择业务字段时,是依据查询时经常用到的字段来进行的。
根据字段值的范围
比如根据ID的值,这里的ID必须是连续的,如果是随机的不规则的就无法进行分了。多个库的ID需要根据统一的ID生成器来生成,而不能使用各个库自己的ID生成器,保证全局ID统一分配。
表0 [0,10000000)
表1 [10000000,20000000)
表2 [20000000,30000000)
表3 [30000000,40000000)
根据日期
比如按照月份,每个月的数据在同一个库,所以在事先建库时要考虑业务的时间线,到什么时候?而且每个月的业务量有多大,不好估计,有可能出现某个月数据量特别巨大的时候,这样原来的问题仍然没有解决。但是对于某种统计类型的数据可以按照这种方式进行分库,比如日志。
取模方式
这种是最普通的一种方式,如根据用户ID取模,
key mod x
,x为库的个数,key为用户ID,不会一开始就上线很多数据库的,一般都是根据业务发展逐渐增加的,所以当x=2时,key=3时,key mod 2 的值为1,即key=3的数据落在1的db上;此时随着业务增加,我们增加库的个数,即x增加到4,那么原来的key=3的数据会落在3的db上,导致数需要从1迁移到3上,迁移数据是最麻烦的,好比飞机空中加油,汽车高速上换轮胎。
Hash一致性方式
首先要理解hash一致性的原理,网上有很多就不在这里赘述了。但是实际情况往往是,我们在具体的分库分表中使用的是这种机制的原理,并不一定是2^32计算法,根据业务系统的估算,单表可以存储的最大存储量来预估整体系统的数据量,从而决定库的个数,这种估算一定要保证几年内业务增长不会重新规划库的数量,否则迁移数据成本太高。计算方式:
key mod x / count * count
,这里的key为业务字段,如用户ID,x为库的个数,需要根据业务数据量的增长来预估,count是每组多少库。
这种方式保证数据可以在每个组是均分的,同时数据也要进行迁移,当组进行扩大时。
以上几种方式也是我们在进行数据库分库分表中常说的水平拆分和垂直拆分的方法。
分库分表的复杂度
并不一定会对所有的表进行拆分,关键是大表,而且对于业务大表来说,拆分之前就要结合实际的业务增长情况判断将来的存储容量和规划。
不同的拆分方式,对于后期当增加节点时,数据迁移的工作量的影响是不同的,而且这种不同是考验一个拆分方法的关键。
拆分依据的分区关键字段,大部分是根据查询条件来决定的,如果是其他查询条件,可以通过建立中间映射表的形式来找到分区关键字,如根据昵称找到Location表的分区ID字段,在根据ID字段找到具体哪个库。但是当查询条件很复杂时,可以通过冗余存储数据来信息复杂条件查询,比如将全量数据同步到ES中提供查询功能。
分库分表后,没有joion操作,分页操作会通过ES或者其他全量单表形式提供,不要在业务逻辑中进行拼装。
Runnf根据实际情况,对于Location和快照表进行分库分表,并依据Hash一致性算法进行。保证在数据量逐步增加时,可以用很少的代价进行数据迁移,保证数据库的性能的同时,保证数据一致性。