三高Mysql - 搭建“三高”架构之复制
引言
内容为慕课网的《高并发 高性能 高可用 MySQL 实战》视频的学习笔记内容和个人整理扩展之后的笔记,这一节讲述搭建Mysql三高架构中的复制,Mysql的复制在实战中实现比较简单,但是Mysql针对复制的内部优化却是一直在进行,这样说明这是值得重视和学习的内容,所以本节针对复制这一特征介绍相关的理论内容。
如果内容比较难可以跟随《Mysql是怎么样运行》个人读书笔记专栏补补课:
地址如下:从零开始学Mysql。
什么是“三高”架构?
三高架构比较好理解,这里简单过一遍:
- 高并发:同时处理多条事务数高
- 高性能:SQL 执行效率高
- 高可用:系统可用率达到99%以上。
三高是目的不是手段
三高架构的关键在于三个关键字:复制,扩展,切换。
复制:数据冗余,binlog传送,并发量提升,可用性提高。缺点是复制会加大服务器的性能开销。
扩展:扩展容量,数据库分片分表,性能和并发量提升。缺点是降低可用性。
切换:主从库提高高可用,主从身份切换,并发量提升。缺点是丢失切换时刻的数据。
这三点对应CAP的理论,CAP中最多只能满足CP或者AP,CAP的理论知识这里略过,结合上面三点可以简单梳理。
三高实现本质:
三高的本质其实就是如何结合复制、扩展、切换三个方法实现三高,我们需要思考下面的三个问题:
- 如何将数据进行冗余?
- 如何有效扩展容量,提高并发性能?
- 如何做主从备份切换,提高高可用?
针对上面三个问题在回到开头提到的三高最终Mysql的三高总结来说就是下面三点:
高并发:复制和拓展,分散多节点
高性能:复制提升速度,拓展容量
高可用:节点之间切换。
复制
复制是Mysql中实现高可用的重要功能,复制类型分为三种:异步复制和半同步复制,组复制的模式以及新版本带来的GTID复制增强模式。
- 异步复制
- 半同步复制
- 组复制(mysql5.7新特性)
- GTID 复制模式(Mysql5.6.5之后新增)(减少复制故障)
复制原理
异步复制
异步复制是非常传统的Mysql复制方式也是实现方式最为简单的一种,异步复制和其名字的意义一样主库在写入binlog之后通知从库数据已经发送然后自己干自己的事情去了,此时从库会主动发起IO请求建立和主库的连接,接着是binlog拷贝到本地写入到relay log中进行重放日志最终sql线程重放然后在最后达到数据一致的效果。
基本的处理步骤如下:
- 主节点执行备份线程,读取binlog文件并且发送给从库。
- 从库的io线程和主库建立连接,使用二进制转储线程读取到binlog文件,如果数据是同步的则睡眠等待主库发送同步信号,否则获取数据把binlog文件保存为relay log,注意从库不会立马执行主库发来的sql,而是会放入到redo log中。
- 从库会通过sql线程定时读取最新的relay log文件,对于relay log重放,重放之后自己再记录一次binlog日志。(自己再记录一次主要是因为从库本身也有可能是其他子从库的主库,整个过程按照相同的步骤处理)
异步复制的问题:
- 读取binlog文件的时候主节点的状态?是否需要锁表?
主节点此时依然可以正常执行,不需要锁表,因为操作的是二进制的binlog文件。- 重放relay log是啥意思?
relay log:中继日志,relay
relay:中继。
重放:重新播放可以认为是重读- 从库复制涉及多少线程
两个,一个IO线程一个SQL线程,IO线程负责从主库获取binlog文件,SQL负责将中继日志进行重放。- 为什么从库最后还需要记录一次binlog?
因为从库也有可能存在自己的子节点,所以也需要按照同样的步骤复制给自己的子节点。- 为什么需要relay log中继日志?
如果备库直接连接主库进行拷贝并且直接执行可能会存在问题,如果此时主库频繁的往binlog塞日志,那么很容易出现主库和备库之间长时间连接并且备库无法正常工作。
异步复制流程图:根据流程图可以看到,在主库执行完sql之后会记录binlog
文件并且commit
事务,通过异步的方式把binlog
发给其他分片上的从库,从库会根据主库的binlog
重放relay log
之后最终记录到binlog
,然后和主库一样完成提交的动作保证数据同步。
问题: 如果主库写入binlog但是此时突然断电,但是Binlog已经发送给从节点,此时会出现什么情况? 没有影响,因为记录binlog意味着已经完成了事务的操作,即时断电主库也可以通过redo log和bin log恢复数据,由于事务已经提交了,发送给从库出现新增数据也是正常的。
异步复制有下面的特点:
- 对于网络延迟具有一定要求
- 实现方式和原理简单。
- 不能保证日志被送到备库可能会出现日志丢失。
通过上面的特点介绍,可以发现异步复制的最大问题就在于异步两个字,由于网络环境的复杂性主库和备库之间是互相分离的,为了确保数据确实送到了从库,Mysql在此基础上改进复制的流程,后面提到的半同步复制其实就在提交之前进行一次“确认”的操作。
半同步复制:
如上面所说的,半同步复制其实就在主库发送binlog文件之后没有立马提交事务,而是等待所有的从库接收到了binlog并且写入到relay log之后才进行事务提交,注意这里并不是等所有的从库提交再提交,而是确认接受到binlog转为relay log之后立马就进行提交。
半同步的复制是延迟了主库一定的提交时间,确保主备数据同步。
问题 :
半同步复制时间等待过久怎么办?
rpl_semi_sync_master_timeout
参数可以配置脱扣时间,脱扣时间是主备库之间的同步过了多少时间超时。
组复制(Mysql Group Replication)
组复制是MySQL5.7版本出现的新特性,组复制的核心是确保数据的强一致性,缺点也很明显会导致数据库系统的响应速度受到影响。
介绍:复制组由多个MySQL Server组成,组中的每个成员可以在任何时候独立执行事务,他们内部使用十分复杂的共识算法进行识别
(核心团体通信系统(GCS)协议),组复制的特点是在复制的时候需要保持强一致性,如下面的图构造显示,和上面提到了复制方式不
同,在组复制的模式下所有的节点是近似平级关系,通过广播的形式通知改动,当主节点发生binlog变动的时候,需要让其他的同级节点
收到通知验证之后才能进行事务的提交。
读者可能会误解组复制让Mysql实现集群了,然而只是有其行没有其本质组复制只不过是用了些新的对概念包装了一些旧东西罢了,可
以看到组复制的最大痛点在于强一致性的等待时间,看起来很美好,数据似乎永远都不会出现故障绝对能保持一致,实际上这个组复制的
等待时间在很多高并发的系统是没法接受的。
组复制的概念出现于比较新的Mysql版本并且在Mysql8.0中被最终完善,这里找了两篇文章供大家拓展阅读:
- MySQL 5.7 基于组复制(MySQL Group Replication) - 运维小结 - 散尽浮华 - 博客园 (cnblogs.com)
- 官方用了一个大节专门吹组复制:MySQL :: MySQL 8.0 Reference Manual :: 18 Group Replication
主从复制实战
这里仅仅记录操作,建议读者根据自己的版本进行实验,注意下面的实验在默认的情况下是异步复制:
- 为了模拟复制可以先弄两台linux虚拟机,比如现代144和146两台服务器,安装了同样为5.7版本的Mysql,这个实验中144为主库,146位从库。
- 两个实验数据库的数据库内容如下:
- 两个服务器都需要修改配置ini文件并且开放binlog,图中为部分配置:
systemctl restart mysqld
重启主库的服务器,此时可以通过命令show master status
和show slave status
来判断是否构成主备架构。- Mysql命令连接主库同时执行
flush tables with read lock
加上全局锁来进行第一次主备数据全量同步,此时可以使用show master status
查看当前binlog的写入的位置,使用mysqldump命令进行全量备份。
全量备份的使用可以阅读:"三高"Mysql - Mysql备份概览中关于Mysqldump复制这一部分的内容。
- 把备份文件到从库上执行
source xxx.sql
实现主备数据之间的同步,注意此时从库需要和主库一样需要将binlog的日志的写入位置进行同步,而binlog文件的写入位置通过主库的show master status
进行查看,比如这里从库就需要同步到主库的.000012
的194位置。
- **关键步骤:**从库如果是slave状态需要通过命令
stop slave
停止slave主库,并且执行reset slave
重置状态,为了和主库保持同步,需要通过下面的命令同步binlog的写入位置,完成之后通过show slave status
检查两边是否同步:
- 最后检查是否正常主备复制同步:
至此,异步主从复制的实战流程结束,如果我们想要实验半同步复制,需要在my.ini
中配置半同步的插件 ,因为半同步复制并不是原生支持的,需要额外的插件支持。
最后通过show variances like 'rpl_semi_sync_master_timeout'
可以查看脱扣时间, 通过show processlist
命令可以查看主节点的当前线程情况。
主节点有下面的线程,可以看到有一个等待Binlog 写入的线程,这是从库等待主库改动binlog的一个线程任务
从节点有两个线程,也可以通过show processlist
方法查看IO现场和重放relay log的两个线程。
GTID增强复制模式
经过上一个小节的介绍,我们发现传统主备节点复制的操作比较麻烦,特别是LOG_FILE + LOG_POS 的方式处理比较麻烦,根本原因是备库不知道从哪一个log开始进行复制,Mysql针对这一点在更高的版本中提供了全局事务的特性,给每一个事务配置一个唯一ID,也就是Mysql5.6的GTID增强模式,GTID就是 server_uuid:gno
组成一个键值对:
- server_uuid(节点的UUID)
- Gno:事务流水号(回滚之后进行回收)
启动GTID模式的配置很简单,在配置文件中加入如下的配置:
- gitd_mode = on
- enforece_gtid_consistency = on
最后使用GTID配置可以修改上一节最后部分提到的change..master
部分:
change master to MASTER_HOST = 'xx.xx.xx.xx' MASTER_USER = 'root' master_auto_position = 1
GTID复制是为了增强主从复制减少故障率而出现的,推荐默认开启
binlog格式格式演变
注意在主从复制中最为关键的binlog格式随着Mysql的升级是做过调整的,在Mysql5.0之前的binlog格式是statement格式,同时内部记录的是原文,而对于一些特殊的语句来说同样的语句可能会有不同的效果,这时候就会有数据风险,比如下面的语句在处理对过程中会出现问题:
为什么要使用row格式
通过上面的介绍,我们大致了解了为什么需要使用row格式,因为row格式不记录SQL语句的原文,而是记录数据行的变化。但是row格式依然没有摆脱记录逻辑日志的这一条规则,而记录文本数据数据量比statement更大,之后会出现空间占用比较大的问题 。
进一步改进:mixed格式的binlog
针对binlog的row格式文本存储的问题,Mysql在提供了一种混合row和statement的对混合模式,对于有数据同步风险的使用row格式,而对于没有风险的则直接使用原文。
另外statement和row格式也称为给予语句的复制和给予行的复制,只是说法上的差别而已本质上并没有差别。
主备延迟如何处理
首先我们需要了解为什么主备之间存在延迟?
- log的传送其实开销比较小,主要的消耗是消费relay log的耗时,
- 备库的性能比主库要小很多
- 备库承担了很多分析SQL的任务,压力比主库要打
- 如果主库有长事务没有提交。
通常主备延迟有下面的解决方式:
- 主备之间使用相同配置的机器
- 备库关闭log实时落盘
- 使用大数据系统分担日志处理任务
但是上面的处理也不是完美的,存在比较大的缺陷,并且通过上面的处理方式之后,依然没有办法完全排除所有的问题,还有诸如备库的性能由于被动接受复制,性能要比主库大打折扣,主库支持“多线程”而备库限制于“单线程”。
总结上面的内容可以发现主备延迟的特点如下:
- 备库延迟主要是备库执行总是要比主库要慢。
- 通过升级备库的硬件和关闭log实时落盘提高性能
- 增加其他的组件分担复制的压力
- 对于新时代的应用系统,使用“组复制”是官方的推荐选择。
针对上面的问题,Mysql对于传统的复制模式提供了更加细分的解决方式:并行复制。并行复制通常有两种思路,第一种是按表分发,第二种是按行分发,以及较新版本出现的事务组并行策略。
并行复制的原理
注意:Mysql 5.6的版本才出现并行复制。
并行复制是在主从复制同步的时候,从库在获取到主库的binlog日志并且保存为relay log之后,把重放relay log的任务另外分配一个叫做 worker的线程执行,sql线程则执行分配relay log的任务,从库只需要读取并分配任务不需要自己进行处理,从而更高效的重放relay log。
难点:如何分配relay log日志。
这里还涉及几个关联问题:事务存在上下文依赖如何处理?如果存在冲突如何分配处理?(比如新增数据同时并行删除)。
并行复制名称看起来比较高大上,但是最大的问题是仅仅只是加快了重访relay log的速度,对于binlog 解析为relay log没有进行更多改进,也就是说把任务分担给了第三者让自己压力小了一点点,但是自己的处理速度和之前基本没太大变化。我们可以通过下面的图进行了解:
并行复制 - 分配思路:
因为并行复制的难点在于如何分发relay log,mysql提供了两种分配方式:按行分配和按表分配。
在最早期还有一种思路出现那就是按库并行的策略:这种处理方式是分发选择方式特别快,同时支持各种的log格式分发,但是同时缺点也非常明显,库粒度非常大并且负载均衡非常难。
开启方式:
salve-parallel-type=DATABASE,这是最初配置,由按照库的方式并行复制,这样的处理方式有下面的特点:
- 分发选择非常快,支持各种log格式
- 难以进行负载均衡,库粒度非常大。因为所有的worker实际上都是分配到一个单独的库进行处理,和之前的单线程处理方式并没有太大的却别。
基于上面的一些问题,在后续的版本中Mysql对于并行复制进行了优化。
当然Mysql不会满足于库的粒度,所以后续基于按库复制基础上出现更多分配的方式:
按行分配:由于binlog记录的是数据行的改动内容,如果修改的不是同一行就可以分配,否则就把他们分配到同一个线程执行。
按表分配:语句按照不同的表进行分类,同一个表的事务放到同一个线程进行分配。
按事务组分配:Mysql5.7提出,使用事务组的方式进行并发提交和处理,下文将会单独介绍。
Mysql按照事务组并行策略(Mysql5.7新特性)
在介绍具体的策略之前,需要解释一下主库对于binlog刷盘的原理,binlog 刷盘分为两步动作,通过这两步动作之后,由此主库的binlog多线程访问,对于多线程事务的提交就可以进行并行刷盘的操作:
- binlog → binlog cache 写到binlog内存文件
- binlog内存文件刷到磁盘中(fsync 持久化磁盘)
事务组并行策略:
下面这图看起来十分复杂,其实简单理解可以认为每一次同步类似我们一次ctrl+s
的动作,我们每一次的保存动作都需要刷新到磁盘,在多线程的操作过程中修改先是修改内存,然后按照顺序的进行刷盘。这里有读者可能会疑问,如果线程之间存在事务交叉怎么办?所以这里会依据binlog刷磁盘的逻辑,按照类似按行分配的处理方式将多线程写入到一个binlog缓冲文件之后一次刷新磁盘。
这样事务组并行缓冲合并刷新到方式,使得并行分配肯定会存在下面两种原则:
- 能够在同一个组里提交的事务,一定不会修改同一行
- 主库上可以并行执行的事务,备库上也一定是可以并行执行的
吐槽:其实这个特性说白了还是“抄”了Mysql原作者的思路,这里提一嘴MariaDB 是在 MySQL 版权被 Oracle 收购后,由 MySQL 创始人 Monty 创立的一个开源数据库,其版权授予了“MariaDB基金会(非营利性组织)。果然最了解儿子的还是亲爹。
下面的结构图是按照上面的文字描述的事物组并行策略的改进图:
可以看到上面的处理方式十分耗费IO性能,并行刷盘频繁浪费性能,可以发现最后一步可以合并前面的并行修改通过一次刷盘完成,所以出现了下面的优化方式:
事务组的含义就是将下面多个并行刷盘的操作合并为同一个,但是这时候又会有一个疑问到底等待多久合并并且刷新一次磁盘?
Mysql 使用了下面的参数进行控制:
(两个条件是或的关系)
- binlog_group_commit_sync_delay:延迟多少微秒之后调用
fsync()
。 - binlog_group_commit_sync_no_dalay_count:类似多少次之后才调用
fsync()
。
事务组并行策略优化(Mysql5.7.22版本)
在5.7版本中还存在过一个小版本的升级对于这个策略做了更多的扩展,比如下面的参数:
binlog-transaction-dependency-tracking 参数
- COMMIT_ORDER:默认策略
- WRITESET:没有修改相同行的事务可以并行。
- WRITESET_SESSION:同一个线程先后执行两个事务不能并行。
强制走主库
如何判断备库已经追上去:
- 强制延时
- seconds_behind_master = 0
- 对比binlog执行位点
- 对比GTID对比情况
但是无法从根本上解决备库延迟的问题,它具备下面几个无法解决的根本性问题:
- binlog 传送和redo log 重放需要时间,这时候受到网络IO或者磁盘IO阻塞的影响
- 备库复制永远只能尽可能减小,无法从本质上完美解决延迟问题。
- 备库因需要
针对次依然可以使用下面的方式判断具体事务是否重放:
- 等待binlog位点:比如通过下面的命令直接监听到具体位置的变动,一旦有变动就认为主库的数据事务完成了。
- 等待GTID(5.7.6之后每次都会返回GTID),通过下面的命令检查唯一事务ID:
简单-双主架构
主-主复制架构一半在一些项目比较小或者一些小公司经常使用,主主复制也就是两个库不存在主备关系,而是通过一个热备的库对于主节点宕机之后临时支撑业务使用。简单理解可以理解为柴油发电机,在停电的时候临时充当使用。
- 两个节点均为Master
- 两个节点均为Salve
- 两库的数据互相复制
- 如果其中一个出现问题,立刻切换到另一个库。
主主架构有下面一些比较明显的问题:
- 数据冲突
- 两边同时插入数据,出现冲突
- 约定好插入不同ID
- 只有一些库可写,另一个只读
- 切换过快的数据库丢失问题。
- 应用切换问题
- 应用自己切换比较麻烦
- keepalived 手段自动切换
- 循环复制问题
- 理论上的问题。
- 未开GTID:使用ServerID过滤。
小结
在复制的部分介绍了复制的基本原理以及Mysql复制的三种方式,在5.6之后还提供了GTID的复制模式使得复制的故障率进一步下降,而针对复制的核心一方面是binlog文件,这里简单的介绍了binlog文件的三种写入格式,最早的statement,常用的row和一种推荐的混合模式,然而混合模式使用频率依然不如row多,内部的机制在实践过程中依然发现不少问题。
而另一方面则是在整个复制过程中插入“中间层”加快部分操作的处理速度,比如在重放relay log中加入一个worker专门负责处理和分发重放relay log的任务,Mysql在这个relay log重放分发的过程中做文章引入了并行复制,并行复制在早期使用按库的力度分配,这虽然很简单好实现但是因为粒度太大被立马改进,后续出现了按行分配和按表分配,最终出现了按事务组的策略分配,这些内容我们只需要理解,并不要去背诵或则牢记。
介绍完主备复制以及相关优化之后,我们切换视角来到了主库这边,主库这边也出现了更优秀的同步策略,那就是直接针对点进行监控,当然这种处理方式比较极端多数情况下不会接触到所以本部分没有过多介绍。
最后我们简单介绍了一下双主的架构,适合大部分的中小公司,对于个人开发的开源项目也能基本应付需求。
写在最后
复制部分仅仅是三高Mysql的第一个大关,后面和还有切换和扩展等着我们介绍,这里也会继续整理给大家带来更多好内容。