1. 为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?
面试官心里分析
其实这块肯定是扯到高并发了,因为分库分表一定是为了支撑高并发,数据量大两个问题的。而且现在说实话,尤其是互联网类的公司面试,基本都会这么来一下,分库分表如此普遍的技术问题,不问实在是不行,而如果你不知道那也实在是说不过去!
面试题剖析
(1)为什么分库分表?(设计高并发系统的时候,数据库层面该如何设计?)
- 部署MySQL单机,扛不住并发
- MySQL单机磁盘容量快满了
- MySQL单表数据量太大了,SQL越跑越慢
说白了,分库分表是两回事,大家别搞混了,可能是光分库不分表,也可能是光分表不分库,都有可能。我先给大家抛出来一个场景。
比如你单表都几千万数据了,你确定你能抗住么?绝对不行,单表数据量太大了,会极大影响你的sql执行的性能,到了后面你的sql可能跑的很慢了,一般来说,就以我的经验来看,单表到几百万的时候,性能就会相对差一点,你就得分表了。
分表是啥意思?就是把一个表的数据放到多个表中,然后查询的时候你就查一个表。比如按照用户id来分表,将一个用户的数据就放到一个表中,然后操作的时候你对一个用户就操作那个表就好了,这样可以控制每个表的数据量在可控的范围内,比如每个表就固定在200万以内。
分库是啥意思?就是你一个库一般我们经验而言,最多支撑到并发2000,一定要扩容了,而且一个健康的单库并发值你最好保持在每秒1000左右,不要太大,那么你可以将一个库的数据拆分到多个库中,访问的时候就访问一个库就好了。
这就是所谓的分库分表,为啥要分库分表?你明白了吗?
(2)用过哪些数据库中间件?不同的分库分表中间件都有什么优点和缺点?
先看看数据库中间件的作用
这个其实就是看看你了解哪些分库分表的中间件,各个中间件的优缺点是啥?然后你用过哪些分库分表的中间件。
比较常见的包括:cobar、TDDL、sharding-jdbc、atlas、mycat
- cobar:阿里b2b团队开发和开源的,属于proxy层(独立部署的)方案。早些年还可以用,但是最近几年都没更新了,基本没啥人用了,差不多算是被抛弃的状态吧。而且不支持读写分离,存储过程、跨库join和分页等操作。
- TDDL:淘宝团队开发的,属于client层方案。不支持join、多表查询等语法,就是基本的crud语法是ok,但是支持读写分离。目前使用的也不多,因为还依赖淘宝的diamond配置管理系统。
- atlas:360开源的,属于proxy层方案,以前是有一些公司在用,但是确实有一个很大的问题就是社区维护好多年没更新了,目前使用的也不多。
- sharding-jdbc:当当开源的,属于client层(直接引用jar,和业务系统在一块的)方案,确实之前用的还比较多一些,因为sql语法支持也比较多,没有太多限制,而且目前推出到了4的版本,支持分库分表、读写分离、分布式id生成、柔性事务(尽最大努力送达型事务、TCC事务)。而且确实之前使用的公司会比较多一些,社区也在一直开发和维护,还算是比较活跃,是一个可以选择的方案。
- mycat:基于cobar改造的,属于proxy层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃。
综上所述:建议使用的就是sharding-jdbc和mycat,sharding-jdbc这种client层方案的优点在于不用部署,运维成本低,但是如果遇到升级啥的需要各个系统都重新升级版本在发布,mycat这种proxy层方案的缺点在于需要部署,自己及运维一套中间件,运维成本高,但是好处在于对各个项目是透明的,如果遇到升级之类的都是自己中间件哪里搞就行。
个人建议中小型公司选用sharding-jdbc,client层方案轻便,不需要额外增派人手,维护成本低,大型公司,可以选用mycat,人员充足,系统和项目很庞大,方便系统升级。
(3)你们具体是如何对数据库进行垂直拆分或水平拆分的?
先对库的数量取模 然后对表的数量取模 最终确定数据落库落表的位置:
水平拆分的意思,就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据,水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来抗更高的并发,还有就是用多个库的存储容量来进行扩容。
垂直拆分的意思,就是把一个有很多字段的表给拆分成多个表,或者是多个库上去,每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会将较小的访问频率很高的字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表里去。因为数据库是有缓存的,你访问评率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。
还有表层面的拆分,就是分表,将一个表变成N个表,就是让每个表的数据量控制在一定范围内,保证SQL的性能。否则单表数据量越大,SQL性能就越差。一般是200万行左右,不要太多,但是也得看具体你怎么操作,也可能是500万,或者是100万。你的SQL越复杂,就最好让单表行数越少。
好了无论是分库了还是分表了,上面说的那些中间件都是可以支持的,就是基本上那些中间件可以做到你分库分表之后,中间件可以根据你指定的某个字段值,比如说userid,自动路由到对应的库上去,然后在自动路由到对应的表里去。
你就得考虑一下,你的项目里该如何分库分表?一般来说,垂直拆分,你可以在表层面来做,对一些字段特别多的表做一下拆分;水平拆分,你可以说是并发承载不了,或者是数据量太大,容量承载不了,你给拆了;按什么字段来拆,你自己想好,分表,你考虑一下,你如果哪怕是拆到每个库里去,并发和容量都ok了,但是每个库的表还是太大了,那么你就分表,将这个表分开,保证每个表的数据量并不是很大。
而且这儿还有两种分库分表的方式,一种是按照range分,就是每个库一段连续的数据,这个一般是按比如时间范围来的,但是这种一般较少使用,因为容易产生热点问题,大量的流量都打在最新的数据上了,或者是按照某个字段hash一下均匀分散,这个较为常用。
range来分,好处在于说,后面扩容的时候,就很容易,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了,缺点,但是大部分的请求,都是访问最新的数据,实际生产用range,要看场景,你的用户不是仅仅访问最新的数据,而是均匀的访问现在的数据以及历史的数据
hash分法,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦
2. 现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态(不停机)切换到分库分表?
面试官心里分析
你看看,你现在已经明白了为啥要分库分表,你也知道常用的分库分表中间件了,你也设计好你们如何分库分表的方案了(水平拆分、垂直拆分、分表),那么问题来了,你接下来该怎么把你那个单库单表的系统给迁移到分库分表上去?
所以这是一环扣一环的,就是看你有没有全流程经历过这个过程
建议自己找找sharding-jdbc和mycat的例子跑一跑
面试题剖析
这个其实从low到高大上有好几种方案,我们都玩过,我给你说一下
(1)长时间停机分库分表
半夜停机,临时写个程序,把老库的数据分别写到几个新库,修改项目配置
(2)双写迁移方案
标准的规范化的表设计里面,都会包含一个最后修改的时间字段,判断一下,分库分表中是否存在?不存在,直接写入,如果存在,比较两个数据的时间戳,如果单库单表读到的数据,比分库分表中读到的数据要新,那么就覆盖分库分表中的数据,绝对不允许旧数据覆盖新数据,只能新数据覆盖旧数据
迁移一轮,600万数据迁移完这么一轮,此时就需要执行一次检查,检查单库单表中的数据,跟分库分表中的数据,是不是一模一样,如果是一模一样的话,那么就ok了,迁移就成功了,如果有差别的话,针对不一样的数据,从单库单表中读取出来,看看是否需要再次覆盖分库分表中的数据
依次循环往复,这个后台程序你可能得跑个好几天,到了凌晨的时候,几乎没有什么新的数据进来了,此时一般来说老库和新库的数据都会变成是一样的
最后一步,就是修改系统的代码,将写单库单表的代码给删除掉,仅仅写分库分表,再次部署一下,就ok了
3. 如何设计可以动态扩容缩容的分库分表方案?
面试官心里分析
(1)选择一个数据库中间件,调研、学习、测试
(2)设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,例如3个库 4个表
(3)基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写
(4)完成单库单表到分库分表的迁移,双写方案
(5)线上系统开始基于分库分表对外提供服务
(6)扩容了,扩容成6个库,每个库需要12个表,你怎么来增加更多的库和表呢?
面试题剖析
(1)停机扩容
这个方案就跟停机迁移一样,步骤几乎一致,唯一的一点就是那个导数据的工具,是把现有库表的数据抽出来慢慢导入新的库表里去,但是最好别这么玩,有点不靠谱,因为既然分库分表就说明数据量实在是太大了,可能多达几亿条甚至几十亿,你这么玩会出问题的。
(2)优化后的方案
一开始上来就是32个库,每个库32张表,1024张表,为啥要1024呢?就是不用改变表的数量,你只是改变数据库服务器的一个数量,不用改变数据库和表的数量
我可以告诉各位同学说,这个分法,第一,基本上国内的互联网肯定都是够用了,第二,无论是并发支持还是数据量支撑都没问题。
这么扩,有一个好处,就是你实际上是不需要将数据读出来,再次分发,直接迁移库的方式,对dba来说,他们有对应的工具可以使用,可以方便快捷很多。
最多可以扩到32个数据库服务器,每个数据库服务器上放一个库,这个库有32张表
按照下图的思路取模,可以让数据更加均匀的扩散
总结:
4. 分库分表之后,id主键如何处理?
面试官心里分析
其实这是分库分表之后你必然要面对的一个问题,就是id咋生成?因为要是分成多个表之后,每个表都是从1开始累加,那肯定不对啊,需要一个全局唯一的id来支持。所以这都是你实际生产环境中必须考虑的问题
面试题剖析
(1)数据库自增id
这个就是说你的系统里每次得到一个id,都是往一个库的一个表插入一条没什么义务含义的数据,然后获取一个数据库自增的一个id,拿到这个id之后在往对应的分库分表里去写入。
这个方案的好处就是方便简单,谁都会用;缺点就是单库生成自增id,要是高并发的话就会有瓶颈,如果你要是硬要改进一下,那么就专门开一个服务出来,这个服务每次就拿当前id最大值,然后自己递增几个id,一次性返回一批id,然后再把当前最大id值修改成递增几个id之后的一最大个值;但是无论怎么说都是基于单个数据库。
适合场景:你分库分表就俩原因,要么就是单库并发太高,要不就是单库数据量太大,磁盘不够了,除非是你并发不高,由于单库数据量太大导致分库分表的扩容,你可以使用这个方案,因为可能每秒最高并发最多就几百,那么就走单独的一个库和表生产自增主键即可。
(2)uuid
好处是本地生成,不要基于数据库来了,不好之处在于,uuid太长了,作为主键性能太差了,不适合用于主键
适合的场景:如果你是要随机生成个什么文件名了,编号之类的,你可以用uuid,但是作为主键是不能用uuid的。
(3)获取系统当前时间戳
这个就是获取当前时间戳,但是问题来了,并发很高的时候,比如一秒并发几千,会有重复的情况,这个是肯定不合适的,基本不用考虑
使用场景:一般如果用这个方案,是将当前实践跟很多其他业务的字段拼接起来,作为一个id,如果业务上你觉得可以接受,那么也是可以的。
(4)snowflake算法
有兴趣的可以看看源码的讲解,加深理解