赞
踩
分库分表是两回事儿,大家可别搞混了,可能是光分库不分表,也可能是光分表不分库,都有可能。
案例说明:
假如我们现在是一个小创业公司(或者是一个 BAT 公司刚兴起的一个新部门),现在注册用户就 20 万,每天活跃用户就 1 万,每天单表数据量就 1000,然后高峰期每秒钟并发请求最多就10个。。。天,就这种系统,随便找一个有几年工作经验的,然后带几个刚培训出来的,随便干干都可以。
结果没想到我们运气居然这么好,碰上个 CEO 带着我们走上了康庄大道,业务发展迅猛,过了几个月,注册用户数达到了 2000 万!每天活跃用户数 100 万!每天单表数据量 10 万条!高峰期每秒最大请求达到 1000!同时公司还顺带着融资了两轮,进账了几个亿人民币啊!公司估值达到了惊人的几亿美金!这是小独角兽的节奏!
好吧,没事,现在大家感觉压力已经有点大了,为啥呢?因为每天多 10 万条数据,一个月就多 300 万条数据,现在咱们单表已经几百万数据了,马上就破千万了。但是勉强还能撑着。高峰期请求现在是 1000,咱们线上部署了几台机器,负载均衡搞了一下,数据库撑 1000QPS 也还凑合。但是大家现在开始感觉有点担心了,接下来咋整呢…
再接下来几个月,我的天,CEO 太牛逼了,公司用户数已经达到 1 亿,公司继续融资几十亿人民币啊!公司估值达到了惊人的几十亿美金,成为了国内今年最牛逼的明星创业公司!天,我们太幸运了。
但是我们同时也是不幸的,因为此时每天活跃用户数上千万,每天单表新增数据多达 50 万,目前一个表总数据量都已经达到了 两三千万 了!扛不住啊!数据库磁盘容量不断消耗掉!高峰期并发达到惊人的 5000~8000
!别开玩笑了,哥。我跟你保证,你的系统支撑不到现在,已经挂掉了!
好吧,所以你看到这里差不多就理解分库分表是怎么回事儿了,实际上这是跟着你的公司业务发展走的,你公司业务发展越好,用户就越多,数据量越大,请求量越大,那你单个数据库一定扛不住。
QPS请求的压力
数据库表数据量以及大量写操作所导致的磁盘占用率极高
sql查询效率
因此,高并发下的大量读写操作,分库分表是非常必要的!
拆了之后带来的好处:读写分离,请求均分,数据均分
就是一个库以经验而言,最多支撑到并发 2000,一定要扩容了。而且一个健康的 单库并发值 ,最好保持在 每秒1000 左右 ,不要太大。如果高并发下,QPS达到万级别的呢?那么你可以将一个库的数据拆分到多个库中,访问的时候就访问一个库好了。也就是说如果有5000左右QPS,把每个库的QPS维持在 1000左右,分发到五个库左右。
比如你单表都几千万数据了,你确定你能扛住么?绝对不行,单表数据量太大,会极大影响你的 sql 执行的性能,到了后面你的 sql 可能就跑的很慢了。一般来说,就以我的经验来看,单表到几百万的时候,性能就会相对差一些了,你就得分表了。
# | 分库分表前 | 分库分表后 |
---|---|---|
并发支撑情况 | MySQL 单机部署,扛不住高并发 | MySQL从单机到多机,能承受的并发增加了多倍 |
磁盘使用情况 | MySQL 单机磁盘容量几乎撑满 | 拆分为多个库,数据库服务器磁盘使用率大大降低 |
SQL 执行性能 | 单表数据量太大,SQL 越跑越慢 | 单表数据量减少,SQL 执行效率明显提升 |
数据库中间件是处于数据库与应用程序之间提供通用、复用服务的系统,减少应用结构的复杂性。如开源的mycat,是由java编写。它的作用其实就是将数据分发到哪个库表。如消费者从MQ队列中消费了消息后,需要写到哪个库中,就需要到了数据库中间件。
应用程序连接数据库中间件用的是标准的数据库协议如jdbc,而数据库中间件在与各种数据库通讯时用的是各数据库的协议。这样在应用程序中就可以透明化的使用数据库。减少开发成本,与适配数据库所带来开发成本。
大公司用 mycat ,小公司用 sharding-jdbc
Proxy 代理模式
Client 客户端模式
在应用程序和数据库中间,单独部署一个代理层,所有的连接和数据库操作都发给这个代理层,由代理层去做底层的实现。
这样做对开发人员来说,是完全不需要知道下面做了什么的,甚至不需要做任何的代码改造,就可以完成接入;当然 Proxy 代理模式对代理层的高可用提出了很高的挑战,实现起来也很复杂。
常见的框架有:MyCat(支持 MySQL, Oracle, DB2, PostgreSQL, SQL Server等主流数据库,基于Cobar改造的)、Cobar(阿里,已停止维护)、MySQL-Proxy、Atlas(360开源的)、sharing-sphere(当当)等等。
这种方式需要对现有程序进行改造,项目代码中需要加入分库分表功能的框架,同时也需要对代码中的配置或 SQL 做相应的修改。
Client 的模式,不需要有代理层,也就不需要考虑代理层高可用的问题(去中心化),实现起来也相对简单;当然缺点也很明显,代码的侵入性比较强,并且需要考虑版本升级的问题。
缺点 :比如说sharding-jdbc,各个系统都需要耦合sharding-jdbc的依赖,如果它升级了,各个系统都需要重新升级版本了才能再发布。
常见的框架有:TDDL(阿里,新名字DRDS)、zebra(美团)、sharding-jdbc(当当,这个做的也不错)等等。
阿里 b2b 团队开发和开源的,属于 proxy 层方案,就是介于应用服务器和数据库服务器之间。应用程序通过 JDBC 驱动访问 Cobar 集群,Cobar 根据 SQL 和分库规则对 SQL 做分解,然后分发到 MySQL 集群不同的数据库实例上执行。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 join 和分页等操作。
淘宝团队开发的,属于 client 层方案。支持基本的 crud 语法和读写分离,但不支持 join、多表查询等语法。目前使用的也不多,因为还依赖淘宝的 diamond 配置管理系统。
360 开源的,属于 proxy 层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是社区最新的维护都在 5 年前了。所以,现在用的公司基本也很少了。
当当开源的,属于 client 层方案,目前已经更名为 ShardingSphere
(后文所提到的 Sharding-jdbc
,等同于 ShardingSphere
)。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,没有太多限制,而且截至 2019.4,已经推出到了 4.0.0-RC1
版本,支持分库分表、读写分离、分布式 id 生成、柔性事务(最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也可以选择的方案。
基于 Cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于 Sharding jdbc 来说,年轻一些,经历的锤炼少一些。
综上,现在其实建议考量的,就是 Sharding-jdbc 和 Mycat,这两个都可以去考虑使用。
Sharding-jdbc 这种 client 层方案的优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合 Sharding-jdbc 的依赖;
Mycat 这种 proxy 层方案的缺点在于需要部署,自己运维一套中间件,运维成本高,但是好处在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了。
通常来说,这两个方案其实都可以选用,但是我个人建议中小型公司选用 Sharding-jdbc,client 层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,项目也没那么多;但是中大型公司最好还是选用 Mycat 这类 proxy 层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护 Mycat,然后大量项目直接透明使用即可。
就是 把一个有很多字段的表给拆分成多个表,或者是多个库上去 。每个库表的结构都不一样,每个库表都包含部分字段。同时垂直拆分后的表,都需要一个相同的主键id作为标识。
一般来说,会将 访问频率很高的字段 放到一个表里去,将访问 频率很低的字段 放到另外一个表里去。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里存放更多的行,性能就越好。这个一般在表层面做的较多一些。
就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来扛更高的并发,还有就是用多个库的存储容量来进行扩容。
按照某个字段 Hash 一下均匀分散,这个较为常用。根据id先计算hash值,然后取模确定分配到哪个库。再根据hash值取模,确定分发到这个库中的哪个表。
优点 :可以平均分配给库的数据量和请求压力。
缺点 :扩容比较麻烦,比如说如果新加机器或加表,那么原来其他表的数据存放的位置就不对了。数据迁移复杂,重新查出来,再重新写入。
另一种是按照 range 来分,就是每个库存储一段连续时间的数据,如2020年1月数据放在库0表0,2020年2月数据放在库0表1等等。这个一般是按比如时间范围来的,但是这种一般较少用。
缺点 :因为 很容易产生数据热点问题 ,如节假日活动大量的流量都打在某个库某个表上了。
sharding-jdbc项目搭建,数据分发教程(可以了解跨库分页等等如何实现)
即停机进行数据迁移,过程中没有数据写入。一般在网站或者 app 上先挂个公告,说 0 点到早上 6 点进行运维,无法访问。
到 0 点停机,系统停掉,没有流量写入了,此时老的单库单表数据库静止了。然后启动之前得写好的 导数的一次性工具 ,此时直接跑起来,然后将单库单表的数据读出来,通过数据库中间件,把数据分发到对应的分库分表去。数据导完之后,重新部署支持分库分表操作的最新项目包,即:修改系统的数据库连接配置,以及修改业务代码中的数据查询、SQL等等。直接启动连到新的分库分表上去。
这个是常用的一种迁移方案,比较靠谱一些,不用停机。
简单来说,就是在线上系统里面,之前所有写库的地方(增删改操作),除了对老库增删改,都加上对新库的增删改,这就是所谓的双写,同时写俩库,老库和新库。
然后 重新系统部署 之后,新库数据差太远,用之前说的 导数工具 ,跑起来读老库数据写新库。
写的时候需要注意,要根据 modify_time 这类字段进行合理判断:
导完一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据。接着反复循环,直到两个库每个表的数据都完全一致为止。基于仅仅使用分库分表的最新代码,重新部署一次,并没有几个小时的停机时间。
假设有4台数据库服务器,每台数据库服务器上创8个库,8个库总共64张表,即每个库8张表。每台数据库服务器的承载写请求1000/s。
如果涉及到动态扩容,一般 由两个原因导致 :
写请求了激增,数据库服务器无法承载写请求。
库表数据量太多了,磁盘快满了。
此时可以再加四台数据库服务器。将每台数据库服务器调整为4个库(表数量不变),然后做数据迁移。此时原来的数据库服务器有一半的数据迁移到了新增的服务器上,同时承载写请求也扩大到了原来的一倍。
库数量变,但是库表的数量不变,是最简单的实现方式。
扩容库的规则:2 ^ n
数据路由规则:
orderId 取模 32 = 库
orderId /(整除) 32 ,再取模 32 = 表
设计一个编码系统,专门维护不同业务所需生成的编码。并维护offset。每次得到一个 id,都从编码库的表里查询,获取偏移量,然后再更新偏移量。
比如说,有一张表专门维护了生产订单的编号偏移量,现在offset是202016,那么查询出offset后,需要自生成下一条数据,即offset = 202017。
适合的场景:分库分表就俩原因,要不就是单库并发太高,要不就是单库数据量太大;除非 并发不高,但是数据量太大 导致的分库分表扩容,可以用这个方案 。
缺点就是单库生成自增 id,要是高并发的话,就会有瓶颈 。因为单库的QPS也就在2000左右。
即redis中维护不同业务的编号offset偏移量。实现思路和数据库自增id类似,即读之后更新offset。
好处就是本地生成,不要基于数据库来了;不好之处就是,UUID 太长了、占用空间大,作为主键性能太差了;更重要的是,UUID 不具有有序性,会导致 B+ 树索引在写的时候有过多的随机写操作(连续的 ID 可以产生部分顺序写),还有,由于在写的时候不能产生有顺序的 append 操作,而需要进行 insert 操作,将会读取整个 B+ 树节点到内存,在插入这条记录后会将整个节点写回磁盘,这种操作在记录占用空间比较大的情况下,性能下降明显。
适合的场景:如果你是要随机生成个什么文件名、编号之类的,你可以用 UUID,但是作为主键是不能用 UUID 的。
首先需保证部署多个实例的多台服务器中时间保证是强一致性的(开启linux的 ntpdate或ntpd服务 )。ntpdate 立即同步时间 。ntpd 逐步缩减时间差 。
但是问题是,并发很高的时候,比如一秒并发几千,会有重复的情况,这个是肯定不合适的。基本就不用考虑了。
适合的场景:一般如果用这个方案,是将当前时间跟很多其他的业务字段拼接起来,作为一个 id,如果业务上你觉得可以接受,那么也是可以的。你可以将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号。
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。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。