大厂-分布式专栏 16 数据库如何做分库分表,读写分离

2022-08-25 18:02:31 浏览数 (1)

16 数据库如何做分库分表,读写分离

宝剑锋从磨砺出,梅花香自苦寒来。——佚名

引言

2016年第一次接触分布式微服务项目后,我在简历上写了我使用了微服务、分库分表技术,那么问题来了,面试官说接下来我们就聊聊分库分表,我信心满满,垂直切分,水平切分,事务问题,都准备好了。

1.面试官:说一说你项目里为什么要分库分表?在什么情况下会使用分库分表。

问题分析:

数据库中的数据量不一定是可控的,在未进行分库分表的情况下,随着时间和业务的发展,库中的表会越来越多,表中的数据也会越来越多,表中的数据量也会越来越大,相应地,数据操作,增删改查的开销也会越来越大,根据个人经验,单表数据在1000w以后,你就要提前规划下后事了,是解决问题还是辞职跑路?所以这一块是分布式系统面试中必问的。

我:

一句话概括就是为了提高数据库的读写的效率,更重要的是提高读效率,提高查询的性能,解决数据量过大从而导致的数据库性能下降的问题,如果业务数据日增量比较大,那么就要提前预估,现有单库单表的数据量读写速度能支撑多久,提前规划时间改造。

举个例子说明,比如用户信息表中现在有3000w条用户数据,此时我们需要在这个表中 insert 一条新的数据,insert 完毕后,数据库会针对用户表重新建立索引,3000w 行数据建立索引的系统开销还是不容忽视的。

假如我们将这个大表分成3个分表,从user_0,user_1,user_2,3000w行数据平均分3张表,每个子表里有1000w行数据,对1000w行的表中insert数据,建立索引的时间就会下降,从而提高了DB的运行时效率,进而提高并发量。

当然分表的好处还不止这些,还有诸如减少写操作时锁范围等,都会带来很多明显的优点。

  1. 分库: 提高架构可用性,减少单点故障,有效分担一个库的压力,只分库不分表,常用于读写分离场景,一主多从,主库负责写,从库用于读,从库从主库同步更新数据,保持数据一致,适用于写入少读取多的场景。
  2. 分表: 把数据分片拆分,多个表数据减少,insert 插入速度提高,读取速度也得以提高。

我的内心:这个问题是不是有点简单呀,这么基础的概念问题。

2.面试官:嗯,你继续,你是怎么做分库分表的?

问题分析: ok,这个问题依旧不难,开始我本想从垂直切分/水平切分这个问题来讲解,后来想想太概念化没意思,直接用每个系统都会有的user表举例,自己理解吧。

我:

还拿用户信息表举例:

1.不分库只分表: 将db库中的user表拆分为2个分表,user_0,user_1,user_2,每个子表里有1000w行数据,这3个表还位于同一个库中,属于水平切分。

2.只分库不分表: 将db库拆分为db_0,db_1,db_2 三个库,同时在db_0,db_1,db_2 库中各自新建一个user表,db_0.user,db_1.user,db_2.user 表中各自只存原来的db.user表中的部分数据,每个子里有1000w行数据。

3.既分库又分表: 将db库拆分为db_0,db_1,db_2 三个库,db_0中包含user_0、user_1两个分表,db_1中包含user_2、user_3两个分表,db_2里有user_4,user_5。这样每张表里有500w行数据。

3.面试官:你是使用什么策略做的分库分表?或者说,根据什么算法拆分数据。

问题分析: 前两个问题我还对答如流,这个问题也ok,我用的取模法呀。

我:

  1. 对key取模法: 项目中我主要是用的这个方法给user表做的分表。 如采用第三种即分库又分表方法为例,根据userId把3000w最终分6张表,n = 6,userId = 10001 取模(10001% 6 = 5)这条数据落在db_2 第user_4分片上,10002 % 6 = 0,落在db_0 第user_0个分片上,以此类推,(注意:分片数从0计数),可以哈希后再取模,Hash(userId)% n ,此种方法数据分布较均匀。
  2. RANGE分区: 这个是我额外了解的方法,userId 从0 - 500w一张表,5000001 - 1000w一张表,依此类推。目前没有在项目中使用过此方法,我觉得这个方法在增量表场景中会造成数据分布不均匀,没道理是用此方法。
  3. 时间分区: 这个方法很实用,适用于订单数据拆分,如按天分表,按月分表,按年分表,时间越久的数据被查询的概率就会越低,类似冷热数据分离,比如我用ES给订单数据做归档,就是使用的按月索引。

到这里我已经我说得很全面了,至少点到了,那么问题来了

面试官: 刚刚你说user表你使用的是对key取模法,比如你2019年用户量 3000w, n = 6可以保证两年增长量不用再拆分,如果业务突飞猛进,到2020年就变两亿用户了, n = 6显然不够,这个时候对key取模法会有什么问题吗?

我陷入了深深的思考…

当时的我被问住了,关于这个问题,我回来后查阅资料总结了一下:

对key取模法有一个缺点:如果n变大,比如现在要把分片数调整为 n=12,那么之前已经计算好的取模值会变,那已经存在的3000w数据是不是要重新分片了?答案是会的,你将要面临大量额外的数据迁移工作,如果这个n变动频繁,是不是考虑还有更好的解决办法,答案就是一致性hash算法,这个算法触及了我的知识盲区,这个算法复杂,可以参考一致性Hash算法及使用场景进一步了解。

关于这个问题,我并不觉得这个算法是更好的,对key取模法没有问题,如果你能估算好数据增量,合理地做好规划,比如5年后需要重刷数据,那5年一次重构也没什么不妥,那个时候你去哪里了都不知道,这个问题只能说明我在了解一个新事物后并没有从更多维度去考虑这个问题,这也是面试官指出的问题所在。

深入分析

这里还有个比较重要的问题面试官没问,就是分库分表,分多少个库比较合适,分多少个表合适,有经验可循吗?

这是我在公司DBA那里得到的经验值,补上,下次面试可以跟面试官多掰扯一下。

表数目决策: 一般情况下,建议单个物理分表的容量不超过1000万行数据。通常可以预估2到5年的数据增长量,用估算出的总数据量除以总的物理分库数,再除以建议的最大数据量1000万,即可得出每个物理分库上需要创建的物理分表数。

库数目决策: 计算公式:按照存储容量来计算 = (3到5年内的存储容量)/ 单个库建议存储容量 (单个库建议存储容量 <300G以内) DBA的操作,一般情况下,会把若干个分库放到一台实例上去。未来一旦容量不够,要发生迁移,通常是对数据库进行迁移。所以库的数目才是最终决定容量大小。 最差情况,所有的分库都共享数据库机器。最优情况,每个分库都独占一台数据库机器。一般建议一个数据库机器上存放8个数据库分库。

分库分表会带来哪些挑战

  1. 分布式ID问题: 在分库分表后,我们不能再使用MySQL的自增主键。因为在插入记录的时候,不同的库生成的记录的自增ID会出现冲突。因此需要有一个全局的ID生成器。系统需要额外的可靠分布式ID生成器服务。
  2. 分布式事务问题: 分布式事务是分库分表绕不过去的一个坎,因为涉及到了同时更新多个数据库,如何保证要么同时成功,要么同时失败。关于分布式事务,MySQL支持XA事务,但是效率较低。柔性事务是目前比较主流的方案,柔性事务包括:最大努力通知型、可靠消息最终一致性方案以及TCC两阶段提交。但是无论XA事务还是柔性事务,实现起来都是非常复杂的。
  3. 最低代价动态扩容问题: 记得一次面试中,上家单位是我第一次使用分库分表,也是用户表,我采用的是取模法将1亿用户分8个表,面试官问我,如果你的用户变成2亿3亿甚至更多,8张表远不够,要分成16张表,这是时候你要重新将几亿数据重新刷盘,你觉得这样合理吗?还有没有什么更好的办法?

这个问题我确实没有想过,因为经验少,例如原来的user表拆分到2个库的4张表上。userId = 10001 取模(10001 % 6 = 5)这条数据落在db_2 第user_4分片上,如果n变成8,10001 % 8 = 1,同样的数据分片变了,如果你不迁移数据,就会出现数据找不到的情况。然而迁移又带来新的成本。解决这个问题可以额外了解下一致性hash算法,这种算法相对复杂,可以移动最少的数据扩容分片数量,但是如果你设计的现有的分片数量,足够业务支撑2年或者更久,那大可不必使用一致性hash算法。

  1. 分库分表后的Join操作: 一般情况下,分库分表后,就不能再和单库单表一样进行Join操作,应尽量避免这样的查询,可以采用字段冗余,这样有些字段就不用join去查询了。

分库分表常用中间件

  1. MyCAT,阿里cobar基础上进行二次开发,解决了cobar当时存在的一些问题,并且加入了许多新的功能在其中。目前MyCAT社区活跃度很高,广受好评的数据库中间件,已经在很多产品上进行使用了。
  2. TDDL,TDDL是Tabao根据自己的业务特点开发了,主要解决了分库分表对应用的透明化以及异构数据库之间的数据复制。
  3. Sharding系列,包括ShardingJDBC,Sharding-Proxy,Sharding-Sidecar,Sharding-JDBC是ShardingSphere的第一个产品,也是ShardingSphere的前身。 它定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。Sharding-JDBC的优势在于对Java应用的友好度。

总结

如果你还没有使用过分库分表,一定要让这一步知识变成你自己的,像你自己做过一样,毕竟你要面试的是一线大厂,分库分表怎能不会?

分库分表常用方案:

  1. 不分库只分表
  2. 只分库不分表
  3. 既分库又分表

分库分表常用数据拆分算法:

  1. 对key取模法
  2. RANGE分区
  3. 时间分区
  4. 一致性Hash

0 人点赞