分库分表

为什么要分库分表

  • 超大容量问题
    (1) 单表超过1000万,就可以考虑分表了
  • 性能问题
    (1) 一般数据库服务器的QPS在800-1200的时候性能最佳,当超过2000的时候sql就会变得很慢并且很容易被请求打死
    (2) 一个健康的单库并发值最好保持在每秒1000左右,不要太大
  • 垂直分库; 解决的是表过多的问题
  • 水平分表; 解决单表列过多的问题

分库分表中间件

  • cobar:阿里b2b团队开发和开源的,属于proxy层方案。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库join和分页等操作。

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

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

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

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

分库分表的方式

  • 按照range来分

    • 就是每个库一段连续的数据,这个一般是按比如时间范围来的
    • 优点: 扩容的时候,就很容易,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了
    • 缺点: 但是大部分的请求,都是访问最新的数据。容易产生热点数据。
      实际生产用range,要看场景,你的用户不是仅仅访问最新的数据,而是均匀的访问现在的数据以及历史的数据
  • 按照某字段的hash来分

    • 按照某个字段hash一下均匀分散,这个较为常用
    • 优点: 可以平均分配每个库的数据量和请求压力
    • 缺点: 扩容起来比较麻烦,会有一个数据迁移的的过程。

动态迁移

如何让未分库分表的系统动态迁移到分库分表?

  • 停机迁移方案
    (1) 通知用户系统需要升级维护时间,然后拒绝用户请求
    (2) 首先建好新的分库分表后的数据库,然后写一个导数据的程序把原先数据库里面的数据入到新库里面,在导完数据后,系统添加分库分表的配置然后重启系统即可。
    (3) 这种方案现在不怎么用了,因为需要停机维护,况且数据量大了会导致停机时间很长,所以说很僵硬。

  • 双写迁移方案

    • 平滑的上线新服务来替换老服务
      (1) 所有写库的地方(增删改操作),除了对老库增删改,也都加上对新库的增删改,这就是所谓双写,同时写俩库
      (2) 读库还是读老库(因为新库数据不完整,没有老数据)

    • 用新服务把老服务都替换掉

    • 将老库特有的数据导入到新库
      (1) 写的时候要根据updateTime这类字段判断这条数据最后修改的时间,除非读出来的数据在新库里没有,或者是比新库的数据新才写。
      (2) 在导完一轮数据后,有可能数据还是存在不一致,那么就用程序自动做一轮校验
      (3) 反复循环,直到两个库每个表的数据都完全一致为止。

    • 接着基于仅仅使用分库分表的最新代码,重新部署一遍

动态扩容

  • 停机扩容
    不靠谱,分库分表就说明数据量实在是太大了,可能多达几亿条,甚至几十亿,需要的时间太长

  • 优化后的方案

    • 一开始上来就是32个库,每个库32个表,1024张表,这个分法。
      开始的时候一个数据库服务器可以放多个数据库和表,在扩容或者缩容的时候直接迁移数据库到其他数据库服务器就可以了。
      第一,基本上国内的互联网肯定都是够用了
      第二,无论是并发支撑还是数据量支撑都没问题。
      步骤如下:
      (1) 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是32库 * 32表,对于大部分公司来说,可能几年都够了
      (2) 路由的规则,orderId 模 32 = 库,orderId / 32 模 32 = 表
      (3) 扩容的时候,申请增加更多的数据库服务器,装好mysql,倍数扩容,4台服务器,扩到8台服务器,16台服务器
      (4) 由dba负责将原先数据库服务器的库,迁移到新的数据库服务器上去,很多工具,库迁移,比较便捷
      (5) 我们这边就是修改一下配置,调整迁移的库所在数据库服务器的地址
      (6) 重新发布系统,上线,原先的路由规则变都不用变,直接可以基于2倍的数据库服务器的资源,继续进行线上系统的提供服务。
  • 并发量分析
    每个库正常承载的写入并发量是1000,那么32个库就可以承载32 * 1000 = 32000的写并发,如果每个库承载1500的写并发,32 * 1500 = 48000的写并发,接近5万/s的写入并发,前面再加一个MQ,削峰,每秒写入MQ 8万条数据,每秒消费5万条数据。除非是国内排名非常靠前的这些公司,他们的最核心的系统的数据库,可能会出现几百台数据库的这么一个规模,128个库,256个库,512个库。

  • 数据量分析
    1024张表,假设每个表放500万数据,在MySQL里可以放50亿条数据,每秒的5万写并发,对于国内大部分的互联网公司来说够用了。

分库分表后,id主键如何生成

  • 数据库自增id
    (1) 这个方案就是每次要获取一个全局id,要向一个单库单表中插入一条无意义的数据来获取他的自增id,在拿到这个id后再在分库分表中使用。
    (2) 这个方案用的人几乎没有,因为分库分表就是因为并法量或者数据量问题,用单库单表去生成主键id,效率很低,不能支持高并发。
    (3) 这个方案还有一个弊端就是生成的是连续的id,如果用于订单什么的可能会暴露商业机密,这就很尴尬。

  • redis的INCR指令
    (1) Redis Incr 命令将 key 中储存的数字值增一,然后返回执行完命令的值
    (2) 这个方案规避了基于数据库的并发问题,因为redis天然吞吐量高,而且纯内存操作,速度非常快
    (3) 但是就是有一个弊端,生成的是有顺序的id, 会暴露订单号。

  • UUID
    uuid好处就是本地生成,不要基于数据库来了;不好之处就是,uuid太长了,作为主键性能太差了,作为主键是不能用uuid

  • snowflake算法
    现在开发中最常用的方案,各方面来说都不错。
    twitter开源的分布式id生成算法,就是把一个64位的long型的id,1个bit是不用的,用其中的41 bit作为毫秒数,用10 bit作为工作机器id,12 bit作为序列号
    (1) 1 bit:不用,为啥呢?因为二进制里第一个bit为如果是1,那么都是负数,但是我们生成的id都是正数,所以第一个bit统一都是0
    (2) 41 bit:表示的是时间戳,单位是毫秒。41 bit可以表示的数字多达2^41 - 1,也就是可以标识2 ^ 41 - 1个毫秒值,换算成年就是表示69年的时间。
    (3) 10 bit:记录工作机器id,代表的是这个服务最多可以部署在2^10台机器上,也就是1024台机器。但是10 bit里5个bit代表机房id,5个bit代表机器id。意思就是最多代表2 ^ 5个机房(32个机房),每个机房里可以代表2 ^ 5个机器(32台机器)。
    (4) 12 bit:这个是用来记录同一个毫秒内产生的不同id,12 bit可以代表的最大正整数是2 ^ 12 - 1 = 4096,也就是说可以用这个12bit代表的数字来区分同一个毫秒内的4096个不同的id。

  • 跨节点Join的问题
    (1) 解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。
    (2) 如果频次较高,可以考虑添加一张关联表,规避掉join操作,空间换时间。

  • 跨节点的count,order by,group by以及聚合函数问题
    (1) 这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。
    (2) 解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。

  • 跨分片的排序分页
    (1) 我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。如下图所示:

    (2) 上面图中所描述的只是最简单的一种情况(取第一页数据),看起来对性能的影响并不大。但是,如果想取出第10页数据,情况又将变得复杂很多,如下图所示:

    (3) 有些读者可能并不太理解,为什么不能像获取第一页数据那样简单处理(排序取出前10条再合并、排序)。其实并不难理解,因为各分片节点中的数据可能是随机的,为了排序的准确性,必须把所有分片节点的前N页数据都排序好后做合并,最后再进行整体的排序。很显然,这样的操作是比较消耗资源的,用户越往后翻页,系统性能将会越差。那如何解决分库情况下的分页问题呢?有以下几种办法:
    A. 则限定用户只能看前面n页,这个限制在业务上也是合理的,一般看后面的分页意义不大(如果一定要看,可以要求用户缩小范围重新查询)
    B. 分库设计时,一般还有配套大数据平台汇总所有分库的记录,有些分页查询可以考虑走大数据平台。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值