分库分表及读写分离

Posted Java菜鸟日志

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分库分表及读写分离相关的知识,希望对你有一定的参考价值。

分库分表

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


垂直拆分的意思,就是把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会将较少的访问频率很高的字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表里去。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。


表层面的拆分,就是分表,将一个表变成 N 个表,就是让每个表的数据量控制在一定范围内,保证 SQL 的性能。否则单表数据量越大,SQL 性能就越差。一般是 200 万行左右,不要太多,但是也得看具体你怎么操作,也可能是 500 万,或者是 100 万。你的SQL越复杂,就最好让单表行数越少。


两种分库分表的方式:

  • 一种是按照 range来分,就是每个库一段连续的数据,这个一般是按比如时间范围来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。

  • 或者是按照某个字段 hash 一下均匀分散,这个较为常用。

range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是一部分的请求, 都是访问最新的数据。实际生产用range,要看场景。


hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表。


Sharding-jdbc

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


Mycat

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


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

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


如何将单库单表的系统给迁移到分库分表

停机迁移方案

0 点停机,系统停掉,没有流量写入了,此时老的单库单表数据库静止了。然后你之前得写好一个导数的一次性工具,此时直接跑起来,然后将单库单表的数据哗哗哗读出来,写到分库分表里面去。导数完了之后,就 ok 了,修改系统的数据库连接配置啥的,包括可能代码和 SQL 也许有修改,那你就用最新的代码,然后直接启动连到新的分库分表上去。


双写迁移方案

简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作,除了对于库增删改,都加上对新库的增删改,这就是所谓的双写,同时写俩库,老库和新库。然后系统部署之后,新库数据差太远,用之前说的导数工具,跑起来读老库数据写新库,写的时候要根据 gmt_modified 这类字段判断这条数据最后修改的时间,除非是读出来的数据在新库里没有,或者是比新库的数据新才会写。简单来说,就是不允许用老数据覆盖新数据。


导完一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次写。反复循环, 直到两个库每个表的数据都完全一致为止。接着当数据完全一致了,就 ok 了,基于仅仅使用分库分表的最新代码,重新部署一次,不就

仅仅基于分库分表在操作了么,还没有几个小时的停机时间,很稳。所以现在基本玩数据迁 移之类的,都是这么干的。


分库分表如何动态进行扩容缩容

分库分表的扩容,第一次分库分表,就一次性给他分个够,32 个库,1024 张表,可能对一部分的中小型互联网公司来说,已经可以支撑好几年了。一个实践是利用 32 * 32 *来分库分表,即分为 32 个库,每个库里一个表分为 32 张表。一共就是 1024 张表。根据某个 id 先根据 32 取模路由到库,再根据 32 取模路由到库里的表。

步骤总结:


  1. 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 32 库 * 32 表, 对于大部分公司来说,可能几年都够了。

  2. 路由的规则,orderId 模 32 = 库,orderId / 32 模 32 = 表 扩容的时候,申请增加更多的数据库服务器,装好mysql,呈倍数扩容,4 台服务器,扩到 8 台服务器,再到 16 台服务器。

  3. 由 dba 负责将原先数据库服务器的库,迁移到新的数据库服务器上去,库迁移是有一些便捷的工具的。

  4. 重新发布系统,上线,原先的路由规则变都不改变,直接可以基于 n 倍的数据库服务器的资源,继续进行线上系统的提供服务。


分布式 id

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 ,也就是可以标识成这么多个毫秒值,换算成年就是表示69年的时间。

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

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


mysql读写分离

实际上大部分的互联网公司,一些网站,或者是 app,其实都是读多写少。所以针对这个情况,就是写一个主库,但是主库挂多个从库,然后从多个从库来读,那不就可以支撑更高的读并发压力了吗?


主从复制原理

主库将变更写入binlog日志,然后从库连接到主库之后,从库有一个 IO 线程,将主库的 binlog 日志拷贝到自己本地,写入一个 relay 中继日志中。接着从库中有一个 SQL 线程会从中继日志读取 binlog,然后执行 binlog日志中的内容,也就是在自己本地再次执行一遍 SQL,这样就可以保证自己跟主库的数据是一样的。


这里有一个非常重要的一点,就是从库同步主库数据的过程是串行化的,也就是说主库上并行的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行SQL 的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所 以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。而且这里还有另外一个问题,就是如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。


所以 MySQL 实际上在这一块有两个机制,一个是半同步复制,用来解决主库数据丢失问题;一个是并行复制,用来解决主从同步延时问题。


所谓半同步复制,也叫 semi-sync 复制,指的就是主库写入binlog日志之后,就会将强制此时立即将数据同步到从库,从库将日志写入自己本地的 relay log 之后,接着会返回一个ack 给主库,主库接收到至少一个从库的ack 之后才会认为写操作完成了。


所谓并行复制,指的是从库开启多个线程,并行读取 relay log 中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。


一般来说,如果主从延迟较为严重,有以下解决方案:


  1. 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。

  2. 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。

  3. 重写代码,写代码的同学,要慎重,插入数据时马上查询可能查不到。

  4. 如果确实是存在必须先插入,马上要求就查询到,然后立马就要反过来执行一些操作,对这个查询设置直连主库。不推荐这种方法,你要是这么搞,读写分离的意义就丧失了。

以上是关于分库分表及读写分离的主要内容,如果未能解决你的问题,请参考以下文章

分表分库与分区的区别及拆分策略

MyCAT读写分离分库分表

数据库分库分表读写分离的实现原理及使用场景

MySQL+MyCat分库分表 读写分离配置MySQL+MyCat分库分表 读写分离配置

ShardingJdbc-分表;分库分表;读写分离;一主多从+分表;一主多从+分库分表;公共表;数据脱敏;分布式事务

MySQL调优分库分表读写分离高可用