1、为何要用分库分表
当不使用分库分表的状况下,系统的性能瓶颈主要体如今:mysql
- 当面临高并发场景的时候,为了不Mysql崩溃(MySql性能通常的服务器建议2000/s读写并发如下),只能使用消息队列来削峰。
- 受制于单机限制。数据库磁盘容量吃紧。
- 数据库单表数据量太大,sql越跑越慢
而分库分表正是为了解决这些问题,提升数据库读写并发量,磁盘容量大大提升,单表数据量下降,提升查询效率。redis
2、垂直拆分和水平拆分
以表的维度来讲:
- 垂直拆分 指根据表的字段进行拆分,其实很常见,有时候在数据库设计的时候就完成了,属于数据库设计范式,如订单表、订单支付表、商品表。
- 水平拆分 表结构同样,数据进行拆分。如本来的t_order表变为t_order_0,t_order_1,t_order_3
以库的维度来讲:
- 垂直拆分 指把本来的大库,按业务不一样拆到不一样的库(微服务通常都是这么设计的,即专库专用)
- 水平拆分 一个服务对应多个库,每一个库有相同的业务表,如库1有t_order表,库2也有t_order表。业务系统经过数据库中间件或中间层操做t_order表,分库操做对于业务代码透明。
因此,咱们日常说的分库分表,通常都是指的水平拆分算法
3、分库分表工具
主要关注MyCat和sharding-jdbc。sql
二者对比:数据库
- MyCat:基于中间件的形式,提供读写分离、分库、分表功能。只不过很久都没更新了,我使用的是1.6版本,并不支持一个逻辑表的分库、分表同时存在。
- sharding-jdbc:基于jar包中间层的形式,提供读写分离、分库、分表功能。社区较活跃。支持功能强大。
4、分库分表的两种策略
- hash分法,按一个键进行hash取模,而后分发到某张表或库。优势是能够平摊每张表的压力,缺点是扩容时会存在数据迁移问题。
- range分法,按范围或时间分发,好比按某个键的值区间、或建立时间进行分发,优势是能够很方便的进行扩容,缺点是会形成数据热点问题。从分表上说还好,若是是分库,将致使某一个库节点压力过大,节点间负载不均。
这里,我认为最好的分法是hash分库、range分表。由于对库来讲重要得是负载要均衡,对表来讲重要的是能够动态扩容。缓存
5、分库分表数据迁移方案
停机分库分表
方案很简单,就是停机维护时,用后台临时程序基于数据库中间件将老库数据直接分发到须要迁移的数据库安全
此方案的主要缺点是必定会出现几个小时的停机,若是没搞定要回滚,次日继续搞,开发人员心是慌得。服务器
不停机双写方案
主要步骤:网络
- 修改系统中的全部写库的代码,同时写老库和数据库中间件(包括新增、更新、删除操做)
- 而后后台用工具将老库以前的老库数据迁移到新的数据库中间件,注意比对修改时间,若id同样,按修改时间决定是否覆盖。
- 使用后台工具比对一次数据,看是否彻底同样,不同,则后台再用工具进行一次迁移。(避免有些数据由于网络问题没有迁移成功,或业务上的bug致使),这个过程一般须要好几天。
- 以此循环几回,数据彻底同样时,就切换为只读写新库
该方案解决了停机形成的服务不可用。多线程
6、分库分表下的动态扩容问题
在分库分表的状况下,如何在已经分库分表的基础上进一步分库分表提升系统效率,是一个麻烦的问题。特别是基于hash分片的服务器,再次分库分表,通常只能对服务器进行停机,而后将全部数据又基于新的规则插入到不一样的库与表。为了不这种问题,能够在第一次分库分表的时候就将库切分的较细,避免二次扩容。好比:
- 最开始就将库分为32个库,最开始业务量没那么大,能够将多个库放在同一台机器上,之后按照2-4-8-16-32来进行扩容,如最开始2台机器可以知足数据库读写并发,此时一台服务器上有16个库,后来不够了,就扩容为4台机器,每台机器8个库。。。依次类推,这样作的好处是只须要迁移须要迁移的库,而且是按照整个库进行迁移,不须要从新进行分发,同时分库分表的分片机制也不用修改,只须要修改其数据源就好了。
- 对于分表来讲,也能够分的多一些,推荐分为32张表。
- 以上,分为了32个库32张表,总共数据量能够达到32 * 32 = 1024张表,按每张表500万正常的容量来算,能够容纳约50亿数据,足以知足大部分过扩容需求。
- 另外这种方案推荐扩容方案为2-4-8-16-32倍数进行扩容,深层缘由是32是这些数的公倍数,按照约数进行扩容更容易让每一个机器负载的库都同样。
- 须要注意的是若是按照同一分片键进行一样的分片策略分库分表,会致使数据只会达到某库的某表好比1库的1表,2库的2表,(由于库和表的数量也是同样)因此分库咱们能够按32取模策略,分表的话咱们能够按整除以后的余数再对32取模进行分表。
7、全局id的生成策略
几种生成id的方式对比:
往公用的一张表(这张表是自增主键)插入一条数据,获取id的返回值,用这个id再去插入中间件当中去。oracle能够经过自增序列。
缺点:不适合并发高的场景,毕竟不论是自增序列仍是采起自增键的方式来生成,会并发竞争写锁,效率过低。
缺点:uuid太长了,不规则
通常联合其余业务字段拼接做为一个Id,如时间戳+用户id+业务含义编码
缺点:并发高容易重复

原理:前面1位为定值0+41位为时间戳+5位机房id+5位为机器id+12位为序号,惟一须要保证同步的地方是生成一个序号,锁粒度较低。另外这个算法可用于分布式环境中。最大的优势是不须要依赖任何中间件,核心原理是用5位机房id,5位机器id标志了惟一一台机器,因此不须要分布式锁去保证不一样机器生成id的同步性,只须要在当前机器保证生成的序号不同就好了。
原理:利用redis单线程工做线程属性去维护一个自增变量。
8、读写分离
为何要读写分离
- 理论上来讲读写请求不要超过2000/s,若是加了缓存以后,到数据库请求仍是超过2000以上考虑读写分离
- 使得读请求能够在不一样机器并发,用了读写分离以后能够经过动态扩展读服务器增长读效率,这与redis中的主从架构读写分离、copyOnWrite机制的并发容器、以及数据库MVCC机制有点相识,都是经过读请求的数据备份增长读写并发效率。
- 适用于业务场景中,读请求大于写请求的状况,读写分离使得系统可以更多的容纳读请求并发。
读写分离的实现方式
通常来讲是基于mysql自带的主从复制功能。mysql主从复制的流程图以下:

总结mysql的主从复制过程大致是主库有一个进程专门是将将记录的Binlog日志发送到从库,从库有一个io线程(5.6.x以后IO线程能够多线程写入relay日志)将收到的数据写入relay日志当中,另外还有一个SQL进程专门读取relay日志,根据relay日志重作命令(5.7版本以后,从能够并行读取relay log重放命令(按库并行,每一个库一个线程))。
主从同步的三种模式:
异步模式以下图所示,这种模式下,主节点不会主动push bin log到从节点,这样有可能致使failover的状况下,也许从节点没有即时地将最新的bin log同步到本地。

这种模式下主节点只须要接收到其中一台从节点的返回信息,就会commit;不然须要等待直到超时时间而后切换成异步模式再提交;这样作的目的可使主从数据库的数据延迟缩小,能够提升数据安全性,确保了事务提交后,binlog至少传输到了一个从节点上,不能保证从节点将此事务更新到db中。性能上会有必定的下降,响应时间会变长。以下图所示:

全同步模式是指主节点和从节点所有执行了commit并确认才会向客户端返回成功。
读写分离场景下主从延迟可能致使的问题
在代码中插入以后,又查询这样的操做是不可靠,可能致使插入以后,查出来的时候尚未同步到从库,因此查出来为null。如何应对这种状况了?其实并不能从根本上解决这种状况的方案。只能必定程度经过下降主从延迟来尽可能避免。
下降主从延迟的方法有:
- 拆主库,下降主库并发,下降主库并发,此时主从延迟能够忽略不计,但并不能保证必定不会出现上述状况。
- 打开并行复制-但这个效果通常不大,由于写入数据可能只针对某个库并发高,而mysql的并行粒度并不小,是以库为粒度的。
但这并不能根本性解决这个问题,其实面对这种状况最好的处理方式是:
- 重写代码,插入以后不要更新
- 若是确实是存在先插入,立马就能查询到,而后立马执行一些操做,那么能够对这个查询设置直连主库(经过中间件能够办到)
参考:深度探索MySQL主从复制原理