架构设计——数据库高可用架构设计方案
我们在考虑MySQL数据库的高可用架构时,主要考虑如下几方面:
- 如果数据库发生了宕机或者意外中断等故障,能尽快恢复数据库的可用性,尽可能的减少停机时间,保证业务不会因为数据库的故障而中断
- 用作备份、只读副本等功能的非主节点的数据应该和主节点的数据实时或者最终保持一致
- 当业务发生数据库切换时,切换前后的数据库内容应当一致,不会因为数据缺失或者数据不一致而影响业务
关于对高可用的分级我们暂不做详细的讨论,这里只讨论常用高可用方案的优缺点以及选型。
随着人们对数据一致性要求不断的提高,越来越多的方法被尝试用来解决分布式数据一致性的问题,如MySQL自身的优化、MySQL集群架构的优化、Paxos、Raft、2PC算法的引入等。
而使用分布式算法用来解决MySQL数据库数据一致性问题的方法,也越来越被人们所接受,一系列成熟的产品如PhxSQL、MariaDB Galera Cluster、Percona XtraDB Cluster等越来越多的被大规模使用。
随着官方MySQL Group Replication的GA,使用分布式协议来解决数据一致性问题已经成为了主流的方向。期望越来越多优秀的解决方案被提出,MySQL高可用问题也可以被更好的解决。
主从或主主半同步复制
使用双节点数据库,搭建单向或者双向的半同步复制。在5.7以后的版本中,由于lossless replication、logical多线程复制等一些列新特性的引入,使得MySQL原生半同步复制更加可靠。常见架构如下:
通常会和Proxy、Keepalived等第三方软件同时使用,即可以用来监控数据库的健康,又可以执行一系列管理命令。如果主库发生故障,切换到备库后仍然可以继续使用数据库。
优点
- 架构比较简单,使用原生半同步复制作为数据同步的依据
- 双节点,没有主机宕机后的选主问题,直接切换即可
- 双节点,需求资源少,部署简单
缺点
- 完全依赖于半同步复制,如果半同步复制退化为异步复制,数据一致性无法得到保证
- 需要额外考虑HAProxy、Keepalived的高可用机制
半同步复制优化
半同步复制机制是可靠的。如果半同步复制一直是生效的,那么可以认为数据是一致的。但是由于网络波动等一些客观原因,导致半同步复制发生超时而切换为异步复制,这时便不能保证数据的一致性。所以尽可能的保证半同步复制,就可以提高数据的一致性。
该方案同样使用双节点架构,但是在原有半同复制的基础上做了功能上的优化,使半同步复制的机制变得更加可靠。可参考的优化方案如下:
双通道复制
半同步复制由于发生超时后,复制断开,当再次建立起复制时,同时建立两条通道,其中一条半同步复制通道从当前位置开始复制,保证从机知道当前主机执行的进度。另外一条异步复制通道开始追补从机落后的数据。当异步复制通道追赶到半同步复制的起始位置时,恢复半同步复制。
binlog文件服务器
搭建两条半同步复制通道,其中连接文件服务器的半同步通道正常情况下不启用,当主从的半同步复制发生网络问题退化后,启动与文件服务器的半同步复制通道。当主从半同步复制恢复后,关闭与文件服务器的半同步复制通道。
优点
- 双节点,需求资源少,部署简单
- 架构简单,没有选主的问题,直接切换即可
- 相比于原生复制,优化后的半同步复制更能保证数据的一致性
缺点
- 需要修改内核源码或者使用MySQL通信协议。需要对源码有一定的了解,并能做一定程度的二次开发
- 依旧依赖于半同步复制,没有从根本上解决数据一致性问题
高可用架构优化
将双节点数据库扩展到多节点数据库,或者多节点数据库集群。可以根据自己的需要选择一主两从、一主多从或者多主多从的集群。由于半同步复制,存在接收到一个从机的成功应答即认为半同步复制成功的特性,所以多从半同步复制的可靠性要优于单从半同步复制的可靠性。并且多节点同时宕机的几率也要小于单节点宕机的几率,所以多节点架构在一定程度上可以认为高可用性是好于双节点架构。
但由于数据库数量较多,所以需要数据库管理软件来保证数据库的可维护性。可以选择MMM、MHA或者各个版本的Proxy等等。常见方案如下:
MHA+多节点集群
MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新数据的slave提升为新的master,然后将所有其他的slave重新指向新的master,整个故障转移过程对应用程序完全透明。MHA Node运行在每台MySQL服务器上,主要作用是切换时处理二进制日志,确保切换尽量少丢数据。MHA也可以扩展到如下的多节点集群:
优点
- 可以进行故障的自动检测和转移
- 可扩展性较好,可以根据需要扩展MySQL的节点数量和结构
- 相比于双节点的MySQL复制,三节点/多节点的MySQL发生不可用的概率更低
缺点
- 至少需要三节点,相对于双节点需要更多的资源
- 逻辑较为复杂,发生故障后排查问题,定位问题更加困难
- 数据一致性仍然靠原生半同步复制保证,仍然存在数据不一致的风险
- 可能因为网络分区发生脑裂现象。
- 在此我向大家推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
ZooKeeper+Proxy
ZooKeeper使用分布式算法保证集群数据的一致性,使用ZooKeeper可以有效的保证Proxy的高可用性,可以较好地避免网络分区现象的产生。
优点
- 较好的保证了整个系统的高可用性,包括Proxy、MySQL
- 扩展性较好,可以扩展为大规模集群
缺点
- 数据一致性仍然依赖于原生的mysql半同步复制
- 引入ZK,整个系统的逻辑变得更加复杂
共享存储
共享存储实现了数据库服务器和存储设备的解耦,不同数据库之间的数据同步不再依赖于MySQL的原生复制功能,而是通过磁盘数据同步的手段,来保证数据的一致性。
SAN共享储存
SAN的概念是允许存储设备和处理器(服务器)之间建立直接的高速网络(与LAN相比)连接,通过这种连接实现数据的集中式存储。常用架构如下:
使用共享存储时,MySQL服务器能够正常挂载文件系统并操作,如果主库发生宕机,备库可以挂载相同的文件系统,保证主库和备库使用相同的数据。
优点
- 两节点即可,部署简单,切换逻辑简单
- 很好的保证数据的强一致性
- 不会因为MySQL的逻辑错误发生数据不一致的情况
缺点
- 需要考虑共享存储的高可用
- 价格昂贵
DRBD磁盘复制
DRBD是一种基于软件、基于网络的块复制存储解决方案,主要用于对服务器之间的磁盘、分区、逻辑卷等进行数据镜像,当用户将数据写入本地磁盘时,还会将数据发送到网络中另一台主机的磁盘上,这样的本地主机(主节点)与远程主机(备节点)的数据就可以保证实时同步。常用架构如下:
当本地主机出现问题,远程主机上还保留着一份相同的数据,可以继续使用,保证了数据的安全。DRBD是Linux内核模块实现的快级别的同步复制技术,可以与SAN达到相同的共享存储效果。
优点
- 两节点即可,部署简单,切换逻辑简单
- 相比于SAN储存网络,价格低廉
- 保证数据的强一致性
缺点
- 对IO性能影响较大
- 从库不提供读操作
分布式协议
分布式协议可以很好地解决数据一致性问题。比较常见的方案如下:
MySQL Cluster
MySQL Cluster是官方集群的部署方案,通过使用NDB存储引擎实时备份冗余数据,实现数据库的高可用性和数据一致性。
优点
- 全部使用官方组件,不依赖于第三方软件
- 可以实现数据的强一致性
缺点
- 国内使用的较少
- 配置较复杂,需要使用NDB储存引擎,与MySQL常规引擎存在一定差异
- 至少三节点
Galera
基于Galera的MySQL高可用集群, 是多主数据同步的MySQL集群解决方案,使用简单,没有单点故障,可用性高。常见架构如下:
优点
- 多主写入,无延迟复制,能保证数据强一致性
- 有成熟的社区,有互联网公司在大规模的使用
- 自动故障转移,自动添加、剔除节点
缺点
- 需要为原生MySQL节点打wsrep补丁
- 只支持innodb储存引擎
- 至少三节点
Paxos
Paxos算法解决的问题是一个分布式系统如何就某个值(决议)达成一致。这个算法被认为是同类算法中最有效的。Paxos与MySQL相结合可以实现在分布式的MySQL数据的强一致性。常见架构如下:
优点
- 多主写入,无延迟复制,能保证数据强一致性
- 有成熟理论基础
- 自动故障转移,自动添加、剔除节点
缺点
- 只支持InnoDB储存引擎
- 至少三节点
- 在此我向大家推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
主从延迟
在实际的生产环境中,由单台MySQL作为独立的数据库是完全不能满足实际需求的,无论是在安全性,高可用性以及高并发等各个方面。因此,一般来说都是通过集群主从复制(Master-Slave)的方式来同步数据,再通过读写分离(MySQL-Proxy)来提升数据库的并发负载能力进行部署与实施。总结MySQL主从集群带来的作用是:
- 提高数据库负载能力,主库执行读写任务(增删改),备库仅做查询
- 提高系统读写性能、可扩展性和高可用性
- 数据备份与容灾,备库在异地,主库不存在了,备库可以立即接管,无须恢复时间
biglog
binlog是什么?有什么作用?
用于记录数据库执行的写入性操作(不包括查询)信息,以二进制的形式保存在磁盘中。可以简单理解为记录的就是sql语句。binlog 是 mysql 的逻辑日志,并且由 Server层进行记录,使用任何存储引擎的 mysql 数据库都会记录 binlog 日志。在实际应用中, binlog 的主要使用场景有两个:
- 用于主从复制,在主从结构中,binlog 作为操作记录从 master 被发送到 slave,slave服务器从 master 接收到的日志保存到 relay log 中
- 用于数据备份,在数据库备份文件生成后,binlog保存了数据库备份后的详细信息,以便下一次备份能从备份点开始
日志格式
binlog日志有三种格式,分别为STATMENT 、 ROW 和 MIXED。在 MySQL 5.7.7 之前,默认的格式是 STATEMENT , MySQL 5.7.7 之后,默认值是 ROW。日志格式通过 binlog-format 指定。
- STATMENT :基于 SQL 语句的复制,每一条会修改数据的sql语句会记录到 binlog 中
- ROW :基于行的复制
- MIXED :基于 STATMENT 和 ROW 两种模式的混合复制,比如一般的数据操作使用 row 格式保存,有些表结构的变更语句,使用 statement 来记录
我们还可以通过mysql提供的查看工具mysqlbinlog查看文件中的内容,例如:
mysqlbinlog mysql-bin.00001 | morebinlog文件大小和个数会不断的增加,后缀名会按序号递增,例如mysql-bin.00002等。
主从复制原理
可以看到mysql主从复制需要三个线程:master(binlog dump thread)、slave(I/O thread 、SQL thread)
- binlog dump线程: 主库中有数据更新时,根据设置的binlog格式,将更新的事件类型写入到主库的binlog文件中,并创建log dump线程通知slave有数据更新。当I/O线程请求日志内容时,将此时的binlog名称和当前更新的位置同时传给slave的I/O线程
- I/O线程: 该线程会连接到master,向log dump线程请求一份指定binlog文件位置的副本,并将请求回来的binlog存到本地的relay log中
- SQL线程: 该线程检测到relay log有更新后,会读取并在本地做redo操作,将发生在主库的事件在本地重新执行一遍,来保证主从数据同步
基本过程总结
- 主库写入数据并且生成binlog文件。该过程中MySQL将事务串行的写入二进制日志,即使事务中的语句都是交叉执行的
- 在事件写入二进制日志完成后,master通知存储引擎提交事务
- 从库服务器上的IO线程连接Master服务器,请求从执行binlog日志文件中的指定位置开始读取binlog至从库
- 主库接收到从库IO线程请求后,其上复制的IO线程会根据Slave的请求信息分批读取binlog文件然后返回给从库的IO线程
- Slave服务器的IO线程获取到Master服务器上IO线程发送的日志内容、日志文件及位置点后,会将binlog日志内容依次写到Slave端自身的Relay Log(即中继日志)文件的最末端,并将新的binlog文件名和位置记录到master-info文件中,以便下一次读取master端新binlog日志时能告诉Master服务器从新binlog日志的指定文件及位置开始读取新的binlog日志内容
- 从库服务器的SQL线程会实时监测到本地Relay Log中新增了日志内容,然后把RelayLog中的日志翻译成SQL并且按照顺序执行SQL来更新从库的数据
- 从库在relay-log.info中记录当前应用中继日志的文件名和位置点以便下一次数据复制
并行复制
在MySQL 5.6版本之前,Slave服务器上有两个线程I/O线程和SQL线程。I/O线程负责接收二进制日志,SQL线程进行回放二进制日志。如果在MySQL 5.6版本开启并行复制功能,那么SQL线程就变为了coordinator线程,coordinator线程主要负责以前两部分的内容:
上图的红色框框部分就是实现并行复制的关键所在。这意味着coordinator线程并不是仅将日志发送给worker线程,自己也可以回放日志,但是所有可以并行的操作交付由worker线程完成。coordinator线程与worker是典型的生产者与消费者模型。
不过到MySQL 5.7才可称为真正的并行复制,这其中最为主要的原因就是slave服务器的回放与主机是一致的即master服务器上是怎么并行执行的slave上就怎样进行并行回放。不再有库的并行复制限制,对于二进制日志格式也无特殊的要求。为了兼容MySQL 5.6基于库的并行复制,5.7引入了新的变量slave-parallel-type,其可以配置的值有:
- DATABASE:默认值,基于库的并行复制方式
- LOGICAL_CLOCK:基于组提交的并行复制方式
按库并行
每个 worker 线程对应一个 hash 表,用于保存当前正在这个worker的执行队列里的事务所涉及到的库。其中hash表里的key是数据库名,用于决定分发策略。该策略的优点是构建hash值快,只需要库名,同时对于binlog的格式没有要求。但这个策略的效果,只有在主库上存在多个DB,且各个DB的压力均衡的情况下,这个策略效果好。因此,对于主库上的表都放在同一个DB或者不同DB的热点不同,则起不到多大效果:
组提交优化
该特性如下:
- 能够同一组里提交的事务,定不会修改同一行
- 主库上可以并行执行的事务,从库上也一定可以并行执行
具体是如何实现的:
- 在同一组里面一起提交的事务,会有一个相同的commit_id,下一组为commit_id+1,该commit_id会直接写到binlog中
- 在从库使用时,相同commit_id的事务会被分发到多个worker并行执行,直到这一组相同的commit_id执行结束后,coordinator再取下一批
主从延迟
根据主从复制的原理可以看出,两者之间是存在一定时间的数据不一致,也就是所谓的主从延迟。导致主从延迟的时间点:
- 主库 A 执行完成一个事务,写入 binlog,该时刻记为T1
- 传给从库B,从库接受完这个binlog的时刻记为T2
- 从库B执行完这个事务,该时刻记为T3
那么所谓主从延迟,就是同一个事务,从库执行完成的时间和主库执行完成的时间之间的差值,即T3-T1。我们也可以通过在从库执行show slave status,返回结果会显示seconds_behind_master,表示当前从库延迟了多少秒。
seconds_behind_master如何计算的?
- 每一个事务的binlog都有一个时间字段,用于记录主库上写入的时间
- 从库取出当前正在执行的事务的时间字段,跟当前系统的时间进行相减,得到的就是seconds_behind_master,也就是前面所描述的T3-T1
为什么会主从延迟?
正常情况下,如果网络不延迟,那么日志从主库传给从库的时间是相当短,所以T2-T1可以基本忽略。最直接的影响就是从库消费中转日志(relaylog)的时间段,而造成原因一般是以下几种:
- 从库的机器性能比主库要差 比如将20台主库放在4台机器,把从库放在一台机器。这个时候进行更新操作,由于更新时会触发大量读操作,导致从库机器上的多个从库争夺资源,导致主从延迟。不过,目前大部分部署都是采取主从使用相同规格的机器部署
- 从库的压力大 按照正常的策略,读写分离,主库提供写能力,从库提供读能力。将进行大量查询放在从库上,结果导致从库上耗费了大量的CPU资源,进而影响了同步速度,造成主从延迟。对于这种情况,可以通过一主多从,分担读压力;也可以采取binlog输出到外部系统,比如Hadoop,让外部系统提供查询能力
- 大事务的执行 一旦执行大事务,那么主库必须要等到事务完成之后才会写入binlog。比如主库执行了一条insert … select非常大的插入操作,该操作产生了近几百G的binlog文件传输到只读节点,进而导致了只读节点出现应用binlog延迟。因此,DBA经常会提醒开发,不要一次性地试用delete语句删除大量数据,尽可能控制数量,分批进行
- 主库的DDL(alter、drop、create)
- 只读节点与主库的DDL同步是串行进行,如果DDL操作在主库执行时间很长,那么从库也会消耗同样的时间,比如在主库对一张500W的表添加一个字段耗费了10分钟,那么从节点上也会耗费10分钟
- 从节点上有一个执行时间非常长的的查询正在执行,那么这个查询会堵塞来自主库的DDL,表被锁,直到查询结束为止,进而导致了从节点的数据延迟
- 锁冲突 锁冲突问题也可能导致从节点的SQL线程执行慢,比如从机上有一些select .... for update的SQL,或者使用了MyISAM引擎等
- 从库的复制能力 一般场景中,因偶然情况导致从库延迟了几分钟,都会在从库恢复之后追上主库。但若是从库执行速度低于主库,且主库持续具有压力,就会导致长时间主从延迟,很有可能就是从库复制能力的问题。
从库上的执行,即sql_thread更新逻辑,在5.6版本之前,是只支持单线程,那么在主库并发高、TPS高时,就会出现较大的主从延迟。因此,MySQL自5.7版本后就已经支持并行复制了。可以在从服务上设置 slave_parallel_workers为一个大于0的数,然后把slave_parallel_type参数设置为LOGICAL_CLOCK,这就可以了
mysql> show variables like 'slave_parallel%';
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| slave_parallel_type | DATABASE |
| slave_parallel_workers | 0 |
+------------------------+----------+怎么减少主从延迟?
主从同步问题永远都是一致性和性能的权衡,得看实际的应用场景,若想要减少主从延迟的时间,可以采取下面的办法:
- 降低多线程大事务并发的概率,优化业务逻辑
- 优化SQL,避免慢SQL,减少批量操作,建议写脚本以update-sleep这样的形式完成
- 提高从库机器的配置,减少主库写binlog和从库读binlog的效率差
- 尽量采用短的链路,也就是主库和从库服务器的距离尽量要短,提升端口带宽,减少binlog传输的网络延时
- 实时性要求的业务读强制走主库,从库只做灾备,备份