数据库分库分表及动态扩容缩容必知必会

数据库分库分表及动态扩容缩容必知必会

1、常见问题

  1. 为什么分库分表(设计高并发系统的时候,数据库层面该如何设计)?
  2. 用过哪些分库分表的中间件?不同的分库分表中间件都有什么有点和缺点?
  3. 常见高并发业务上,是如何对数据库进行垂直拆分和水平拆分的?
  4. 现在有一个为分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表,动态切换到分库分表上?
  5. 如何设计可以动态扩容缩容的分库分表方案?
  6. 分库分表之后,id主键如何处理?

2、为什么要分库分表?(设计高并发系统的时候,数据库层面该如何设计?)

分库分表是两回事儿,大家可别搞混了,可能是光分库不分表,也可能是光分表不分库,都有可能。

2.1 分库分表的由来

案例说明:
假如我们现在是一个小创业公司(或者是一个 BAT 公司刚兴起的一个新部门),现在注册用户就 20 万,每天活跃用户就 1 万,每天单表数据量就 1000,然后高峰期每秒钟并发请求最多就10个。。。天,就这种系统,随便找一个有几年工作经验的,然后带几个刚培训出来的,随便干干都可以。

结果没想到我们运气居然这么好,碰上个 CEO 带着我们走上了康庄大道,业务发展迅猛,过了几个月,注册用户数达到了 2000 万!每天活跃用户数 100 万!每天单表数据量 10 万条!高峰期每秒最大请求达到 1000!同时公司还顺带着融资了两轮,进账了几个亿人民币啊!公司估值达到了惊人的几亿美金!这是小独角兽的节奏!

好吧,没事,现在大家感觉压力已经有点大了,为啥呢?因为每天多 10 万条数据,一个月就多 300 万条数据,现在咱们单表已经几百万数据了,马上就破千万了。但是勉强还能撑着。高峰期请求现在是 1000,咱们线上部署了几台机器,负载均衡搞了一下,数据库撑 1000QPS 也还凑合。但是大家现在开始感觉有点担心了,接下来咋整呢…

再接下来几个月,我的天,CEO 太牛逼了,公司用户数已经达到 1 亿,公司继续融资几十亿人民币啊!公司估值达到了惊人的几十亿美金,成为了国内今年最牛逼的明星创业公司!天,我们太幸运了。

但是我们同时也是不幸的,因为此时每天活跃用户数上千万,每天单表新增数据多达 50 万,目前一个表总数据量都已经达到了 两三千万 了!扛不住啊!数据库磁盘容量不断消耗掉!高峰期并发达到惊人的 5000~8000!别开玩笑了,哥。我跟你保证,你的系统支撑不到现在,已经挂掉了!

好吧,所以你看到这里差不多就理解分库分表是怎么回事儿了,实际上这是跟着你的公司业务发展走的,你公司业务发展越好,用户就越多,数据量越大,请求量越大,那你单个数据库一定扛不住。

2.2 高并发下,部署MYSQL单机数据库,必然面临的问题

  • QPS请求的压力

  • 数据库表数据量以及大量写操作所导致的磁盘占用率极高

  • sql查询效率

    因此,高并发下的大量读写操作,分库分表是非常必要的!

    拆了之后带来的好处:读写分离,请求均分,数据均分

2.3 高并发下的MYSQL图解

在这里插入图片描述

2.4 分库

就是一个库以经验而言,最多支撑到并发 2000,一定要扩容了。而且一个健康的 单库并发值 ,最好保持在 每秒1000 左右 ,不要太大。如果高并发下,QPS达到万级别的呢?那么你可以将一个库的数据拆分到多个库中,访问的时候就访问一个库好了。也就是说如果有5000左右QPS,把每个库的QPS维持在 1000左右,分发到五个库左右。

2.5 分表

比如你单表都几千万数据了,你确定你能扛住么?绝对不行,单表数据量太大,会极大影响你的 sql 执行的性能,到了后面你的 sql 可能就跑的很慢了。一般来说,就以我的经验来看,单表到几百万的时候,性能就会相对差一些了,你就得分表了。

2.6 分库分表前后对比

#分库分表前分库分表后
并发支撑情况MySQL 单机部署,扛不住高并发MySQL从单机到多机,能承受的并发增加了多倍
磁盘使用情况MySQL 单机磁盘容量几乎撑满拆分为多个库,数据库服务器磁盘使用率大大降低
SQL 执行性能单表数据量太大,SQL 越跑越慢单表数据量减少,SQL 执行效率明显提升

2.7 数据库中间件

数据库中间件是处于数据库与应用程序之间提供通用、复用服务的系统,减少应用结构的复杂性。如开源的mycat,是由java编写。它的作用其实就是将数据分发到哪个库表。如消费者从MQ队列中消费了消息后,需要写到哪个库中,就需要到了数据库中间件。

应用程序连接数据库中间件用的是标准的数据库协议如jdbc,而数据库中间件在与各种数据库通讯时用的是各数据库的协议。这样在应用程序中就可以透明化的使用数据库。减少开发成本,与适配数据库所带来开发成本。

2.7.1 分别分表中间件有哪些?
  • cobar
  • TDDL
  • atlas
  • sharding-jdbc 当当开源产品,属于client方案
  • mycat 基于cobar改造,属于proxy方案

大公司用 mycat ,小公司用 sharding-jdbc

2.7.2 数据库中间件的实现方案基本上有两种
  • Proxy 代理模式

  • Client 客户端模式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DSy418ml-1603200537141)(C:\Users\mocar\AppData\Local\Temp\1602430353144.png)]

2.7.3 Proxy 代理模式 (独立部署)

在应用程序和数据库中间,单独部署一个代理层,所有的连接和数据库操作都发给这个代理层,由代理层去做底层的实现。

这样做对开发人员来说,是完全不需要知道下面做了什么的,甚至不需要做任何的代码改造,就可以完成接入;当然 Proxy 代理模式对代理层的高可用提出了很高的挑战,实现起来也很复杂。

常见的框架有:MyCat(支持 MySQL, Oracle, DB2, PostgreSQL, SQL Server等主流数据库,基于Cobar改造的)、Cobar(阿里,已停止维护)、MySQL-Proxy、Atlas(360开源的)、sharing-sphere(当当)等等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kAdsGpSk-1603200537142)(C:\Users\mocar\AppData\Local\Temp\1602429967750.png)]

2.7.4 Client 客户端模式 (引入jar包)

这种方式需要对现有程序进行改造,项目代码中需要加入分库分表功能的框架,同时也需要对代码中的配置或 SQL 做相应的修改。

Client 的模式,不需要有代理层,也就不需要考虑代理层高可用的问题(去中心化),实现起来也相对简单;当然缺点也很明显,代码的侵入性比较强,并且需要考虑版本升级的问题。

缺点 :比如说sharding-jdbc,各个系统都需要耦合sharding-jdbc的依赖,如果它升级了,各个系统都需要重新升级版本了才能再发布。

常见的框架有:TDDL(阿里,新名字DRDS)、zebra(美团)、sharding-jdbc(当当,这个做的也不错)等等。

3、用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?

3.1 Cobar

阿里 b2b 团队开发和开源的,属于 proxy 层方案,就是介于应用服务器和数据库服务器之间。应用程序通过 JDBC 驱动访问 Cobar 集群,Cobar 根据 SQL 和分库规则对 SQL 做分解,然后分发到 MySQL 集群不同的数据库实例上执行。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 join 和分页等操作。

3.2 TDDL

淘宝团队开发的,属于 client 层方案。支持基本的 crud 语法和读写分离,但不支持 join、多表查询等语法。目前使用的也不多,因为还依赖淘宝的 diamond 配置管理系统。

3.3 Atlas

360 开源的,属于 proxy 层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是社区最新的维护都在 5 年前了。所以,现在用的公司基本也很少了。

3.4 Sharding-jdbc

当当开源的,属于 client 层方案,目前已经更名为 ShardingSphere(后文所提到的 Sharding-jdbc,等同于 ShardingSphere)。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,没有太多限制,而且截至 2019.4,已经推出到了 4.0.0-RC1 版本,支持分库分表、读写分离、分布式 id 生成、柔性事务(最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也可以选择的方案

3.5 Mycat

基于 Cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于 Sharding jdbc 来说,年轻一些,经历的锤炼少一些。

3.6 总结

综上,现在其实建议考量的,就是 Sharding-jdbc 和 Mycat,这两个都可以去考虑使用。

Sharding-jdbc 这种 client 层方案的优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合 Sharding-jdbc 的依赖;

Mycat 这种 proxy 层方案的缺点在于需要部署,自己运维一套中间件,运维成本高,但是好处在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了。

​ 通常来说,这两个方案其实都可以选用,但是我个人建议中小型公司选用 Sharding-jdbc,client 层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,项目也没那么多;但是中大型公司最好还是选用 Mycat 这类 proxy 层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护 Mycat,然后大量项目直接透明使用即可。

4、如何对数据库进行垂直拆分和水平拆分的?

4.1 垂直拆分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ci7kx2eD-1603200537143)(C:\Users\mocar\AppData\Local\Temp\1602432491509.png)]

就是 把一个有很多字段的表给拆分成多个表或者是多个库上去 。每个库表的结构都不一样,每个库表都包含部分字段。同时垂直拆分后的表,都需要一个相同的主键id作为标识。

一般来说,会将 访问频率很高的字段 放到一个表里去,将访问 频率很低的字段 放到另外一个表里去。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里存放更多的行,性能就越好。这个一般在表层面做的较多一些。

4.2 水平拆分

在这里插入图片描述
就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来扛更高的并发,还有就是用多个库的存储容量来进行扩容。

4.3 数据分发算法

  • 按照某个字段 Hash 一下均匀分散,这个较为常用。根据id先计算hash值,然后取模确定分配到哪个库。再根据hash值取模,确定分发到这个库中的哪个表。

    优点 :可以平均分配给库的数据量和请求压力。

    缺点 :扩容比较麻烦,比如说如果新加机器或加表,那么原来其他表的数据存放的位置就不对了。数据迁移复杂,重新查出来,再重新写入。

  • 另一种是按照 range 来分,就是每个库存储一段连续时间的数据,如2020年1月数据放在库0表0,2020年2月数据放在库0表1等等。这个一般是按比如时间范围来的,但是这种一般较少用。

    缺点 :因为 很容易产生数据热点问题 ,如节假日活动大量的流量都打在某个库某个表上了。

5、如何设计从未分库分表,动态切换到分库分表上?

sharding-jdbc项目搭建,数据分发教程(可以了解跨库分页等等如何实现)

5.1 线上数据迁移方案

5.1.1 停机迁移方案

即停机进行数据迁移,过程中没有数据写入。一般在网站或者 app 上先挂个公告,说 0 点到早上 6 点进行运维,无法访问。

到 0 点停机,系统停掉,没有流量写入了,此时老的单库单表数据库静止了。然后启动之前得写好的 导数的一次性工具 ,此时直接跑起来,然后将单库单表的数据读出来,通过数据库中间件,把数据分发到对应的分库分表去。数据导完之后,重新部署支持分库分表操作的最新项目包,即:修改系统的数据库连接配置,以及修改业务代码中的数据查询、SQL等等。直接启动连到新的分库分表上去。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bgITPRLY-1603200537144)(C:\Users\mocar\AppData\Local\Temp\1602600958837.png)]

5.1.2 双写迁移方案

这个是常用的一种迁移方案,比较靠谱一些,不用停机。

简单来说,就是在线上系统里面,之前所有写库的地方(增删改操作),除了对老库增删改,都加上对新库的增删改,这就是所谓的双写,同时写俩库,老库和新库。

然后 重新系统部署 之后,新库数据差太远,用之前说的 导数工具 ,跑起来读老库数据写新库。

写的时候需要注意,要根据 modify_time 这类字段进行合理判断:

  • 如果读出来的数据,新库里没有,直接写入。modify_time 不能用当前写入的时间,应该是原来数据modify_time 是什么就是什么。
  • 如果读出来的数据,新库里有,要根据 modify_time 这类字段和新库的作比较。如果老库里读的数据时间比分库分表的新,则更新,其他情况不做操作。简单来说,就是不允许用老数据覆盖新数据。

导完一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据。接着反复循环,直到两个库每个表的数据都完全一致为止。基于仅仅使用分库分表的最新代码,重新部署一次,并没有几个小时的停机时间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDWqHopF-1603200537144)(C:\Users\mocar\AppData\Local\Temp\1602601822104.png)]

6、如何设计可以动态扩容缩容的分库分表方案?

6.1 动态扩容场景

假设有4台数据库服务器,每台数据库服务器上创8个库,8个库总共64张表,即每个库8张表。每台数据库服务器的承载写请求1000/s。

如果涉及到动态扩容,一般 由两个原因导致

  • 写请求了激增,数据库服务器无法承载写请求。

  • 库表数据量太多了,磁盘快满了。

    此时可以再加四台数据库服务器。将每台数据库服务器调整为4个库(表数量不变),然后做数据迁移。此时原来的数据库服务器有一半的数据迁移到了新增的服务器上,同时承载写请求也扩大到了原来的一倍。

6.2 如何设计动态扩容呢?

库数量变,但是库表的数量不变,是最简单的实现方式。

扩容库的规则:2 ^ n

数据路由规则:

orderId 取模 32 = 库

orderId /(整除) 32 ,再取模 32 = 表

7、分库分表之后,id主键如何处理

7.1 数据库自增ID

设计一个编码系统,专门维护不同业务所需生成的编码。并维护offset。每次得到一个 id,都从编码库的表里查询,获取偏移量,然后再更新偏移量。

比如说,有一张表专门维护了生产订单的编号偏移量,现在offset是202016,那么查询出offset后,需要自生成下一条数据,即offset = 202017。

适合的场景:分库分表就俩原因,要不就是单库并发太高,要不就是单库数据量太大;除非 并发不高,但是数据量太大 导致的分库分表扩容,可以用这个方案 。

缺点就是单库生成自增 id,要是高并发的话,就会有瓶颈 。因为单库的QPS也就在2000左右。

7.2 Redis中维护offset

即redis中维护不同业务的编号offset偏移量。实现思路和数据库自增id类似,即读之后更新offset。

7.3 UUID

好处就是本地生成,不要基于数据库来了;不好之处就是,UUID 太长了、占用空间大,作为主键性能太差了;更重要的是,UUID 不具有有序性,会导致 B+ 树索引在写的时候有过多的随机写操作(连续的 ID 可以产生部分顺序写),还有,由于在写的时候不能产生有顺序的 append 操作,而需要进行 insert 操作,将会读取整个 B+ 树节点到内存,在插入这条记录后会将整个节点写回磁盘,这种操作在记录占用空间比较大的情况下,性能下降明显。

适合的场景:如果你是要随机生成个什么文件名、编号之类的,你可以用 UUID,但是作为主键是不能用 UUID 的。

7.4 获取系统当前时间

首先需保证部署多个实例的多台服务器中时间保证是强一致性的(开启linux的 ntpdate或ntpd服务 )。ntpdate 立即同步时间 。ntpd 逐步缩减时间差 。

但是问题是,并发很高的时候,比如一秒并发几千,会有重复的情况,这个是肯定不合适的。基本就不用考虑了。

适合的场景:一般如果用这个方案,是将当前时间跟很多其他的业务字段拼接起来,作为一个 id,如果业务上你觉得可以接受,那么也是可以的。你可以将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号。

7.5 snowflake算法

snowflake 算法是 twitter 开源的分布式 id 生成算法,采用 Scala 语言实现,是把一个 64 位的 long 型的 id,1 个 bit 是不用的 + 用其中的 41 bit 作为毫秒数 + 用 10 bit 作为工作机器 id + 12 bit 作为序列号。

  • 1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。

  • 41 bit:表示的是时间戳,单位是毫秒。41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2^41 - 1 个毫秒值,换算成年就是表示69年的时间。

  • 10 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10台机器上哪,也就是1024台机器。但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2^5个机房(32个机房),每个机房里可以代表 2^5 个机器(32台机器)。

  • 12 bit:这个是用来记录同一个毫秒内产生的不同 id,12 bit 可以代表的最大正整数是 2^12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 id。

    之前有写过关于snowflake 雪花算法的日志

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值