三高 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
部分:
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 的第一个大关,后面和还有切换和扩展等着我们介绍,这里也会继续整理给大家带来更多好内容。
评论