MySQL 8.0主从复制模型的示例分析

69次阅读
没有评论

共计 5627 个字符,预计需要花费 15 分钟才能阅读完成。

这篇文章给大家分享的是有关 MySQL 8.0 主从复制模型的示例分析的内容。丸趣 TV 小编觉得挺实用的,因此分享给大家做个参考,一起跟随丸趣 TV 小编过来看看吧。

一、MySQL 主从复制模型

一切都要从 MySQL 的主从复制模型开始说起,下图是最经典的 MySQL 主从复制模型架构图:

 

MySQL 复制模型

MySQL 的主从架构依赖于 MySQL Binlog 功能,Master 节点上产生 Binlog 并将 Binlog 写入到 Binlog 文件中。

Slave 节点上启动两个线程:一个 IO 线程,从 MySQL 上捞取 Binlog 日志并写入到本地的 RelayLog 日志;另一个 SQL 线程,不断从 RelayLog 日志中读取日志,并解析执行,这样通过在主机和从机上增加几个文件的顺序读写操作,就可以保证所有在主机上执行过的 SQL 语句都在从机上一摸一样的执行过一遍。

复制延迟,指的就是一个事务在 Master 执行完成以后,要多久以后才能在 Slave 上执行完成。

由于对 Binlog 文件以及 RelayLog 文件的读写均为顺序操作,在生产环境中,Slave 上的 IO 线程对 Binlog 文件的 Dump 操作是很少产生延迟的。实际上,从 MySQL 5.5 开始,MySQL 官方提供了半同步复制插件,每个事务的 Binlog 需要保证传输到 Slave 写入 RelayLog 后才能提交,这种架构在主从之间提供了数据完整性,保证了主机在发生故障后从机可以拥有完整的数据副本。因此,复制延迟通常发生在 SQL 线程执行的过程中。

从架构图上可以看到,最早的主从复制模型中,只有一个线程负责执行 Relaylog,也就是说所有在主机上的操作,在从机上是串行回放的。这就带来一个问题,如果主上写入压力比较大,那么从上的回放速度很有可能会一直跟不上主。(除此之外,MySQL 的架构决定了 Binlog 只有在 Commit 阶段才会写入 Binlog 文件并 Dump 给从机,这也导致主从事务必然有执行延迟,这个问题在大事务中体现的特别明显,不过这个问题就不在本文的讨论范围内了)

既然主从延迟的问题是单线程回放 RelayLog 太慢,那么减少主从延迟的方案自然就是提高从机上回放 RelayLog 的并行度。

二、5.7 中的并行复制

1、Schema 级别的并行复制

MySQL 官方在 5.6 中引入了一个比较简单并行复制方案,其架构如下:

 

(图片来自姜承尧老师的博客)

红色框部分为并行回放的关键,5.6 中若开启并行回放的功能,便会启动多个 WorkThread,而原来负责回放的 SQLThread 会转变成 Coordinator 角色,负责判断事务能否并行执行并分发给 WorkThread。

如果事务分别属于不同的 Schema,并且不是 DDL 语句,同时没有跨 Schema 操作,那么就可以并行回放,否则需要等所有 Worker 线程执行完成后再执行当前日志中的内容。

这种并行回放是 Schema 级别的并行,如果实例上有多个 Schema 将会因此收益,而如果实例上只有一个 Schema,那么事务将无法并行回放,而且还会因多了分发的操作导致效率略微下降。而在实际应用中,单库多表才是更常见的情况。

2、基于 Group Commit 的并行复制

虽然 5.6 中的并行复制在大多数应用场景中对回放速度的提升不大,但是该架构却成为了后来 MySQL 并行复制的基础——即在 Slave 上并行回放 RelayLog,SQL 线程负责判断能否并行回放,并分配给 Work 线程回放。

5.6 中引入 Group Commit 技术,是为了解决事务提交的时候需要 fsync 导致并发性不够而引入的。简单来说,就是由于事务提交时必须将 Binlog 写入到磁盘上而调用 fsync,这是一个代价比较高的操作,事务并发提交的情况下,每个事务各自获取日志锁并进行 fsync 会导致事务实际上以串行的方式写入 Binlog 文件,这样就大大降低了事务提交的并发程度。

5.6 中采用的 Group Commit 技术将事务的提交阶段分成了 Flush、Sync、Commit 三个阶段,每个阶段维护一个队列,并且由该队列中第一个线程负责执行该步骤,这样实际上就达到了一次可以将一批事务的 Binlog fsync 到磁盘的目的,这样的一批同时提交的事务称为同一个 Group 的事务。

Group Commit 虽然是属于并行提交的技术,但是却意外解决了从机上事务并行回放的一个难题——即如何判断哪些事务可以并行回放。如果一批事务是同时 Commit 的,那么这些事务必然不会有互斥的持有锁,也不会有执行上的相互依赖,因此这些事务必然可以并行的回放。

因此 MySQL 5.7 中引入了新的并行回放类型,由参数 slave_parallel_type 决定,默认值 DATABASE 将会采用 5.6 版本中的 SCHEMA 级别的并行回放,设置为 LOGICAL_LOCK 则会采用基于 GroupCommit 的并行回放,同一个 Group 内的事务将会在 Slave 上并行回放。

为了标记事务所属的组,MySQL 5.7 版本在产生 Binlog 日志时会有两个特殊的值记录在 Binlog Event 中,last_committed 和 sequence_number,其中 last_committed 指的是该事务提交时,上一个事务提交的编号,sequence_number 是事务提交的序列号,在一个 Binlog 文件内单调递增。如果两个事务的 last_committed 值一致,这两个事务就是在一个组内提交的。

如上 binlog 文件中,sequence_number 1- 6 的事务 last_committed 都是 0,因此属于同一个组,可以在 slave 上并行回放,7-12 的 last_committed 都是 6,也属于同一个组,因此可以并行回放。

5.7 中引入的基于 Logical_Lock 极大的提高了在主机并发压力比较大的情况下从机上的回放速度,基本上做到了主机上如何提交的,在从机上如何回放。

三、MySQL MGR 中的 WriteSet

虽然如此,在 5.7 中,基于逻辑时钟 Logical_Clock 的并行复制仍然有不尽人意的地方,比如必须是在主上并行提交的事务才能在从上并行回放,如果主上并发压力不大,那么就无法享受到并行复制带来的好处。5.7 中引入了 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 两个参数,通过让 Binlog 在执行 fsync 前等待一小会来提高 Master 上组提交的比率。但是无论如何,从上并行回放的速度还是取决于主上并行提交的情况。

MySQL 8.0 中引入了一种新的机制来判断事务能否并行回放,通过检测事务在运行过程中是否存在写冲突来决定从机上的回放顺序,这使得从机上的并发程度不再依赖于主机。

事实上,该机制在 MySQL 5.7.20 版本中就已经悄悄的应用了。5.7.20 版本引入了一个重要的特性:Group Replication,通过 Paxso 协议在多个 MySQL 节点间分发 binlog,使得一个事务必须在集群内大多数节点 (N/2+1) 上提交成功才能提交。

为了支持多主写入,MySQL MRG 在 Binlog 分发节点完成后,通过一个 Certify 阶段来决定 Binlog 中的事务是否写入 RelayLog 中。这个过程中,Certify 阶段采用的就是 WriteSet 的方式验证事务之间是否存在冲突,同时,在写入 RelayLog 时会将没有冲突的事务的 last_committed 值设置为相同的值。

比如在 5.7.20 中,进行如下操作:

以上代码在一个 MGR 集群中创建了一个数据库和一个 InnoDB 表,并插入了三条记录。这个时候,查询 Primary 节点上的 Binlog 可能会得到如下结果:

可以看到,由于是在一个 Session 中,这些操作按着串行的顺序有着不同的 last_committed,正常情况下,这些 BinlogEvent 应该在从机上同样以串行的方式回放。我们看一下在 MGR 集群中的 RelayLog 情况:

有趣的是,在 Secondary 节点的 RelayLog 中, 这些事务有着相同的 last_committed 值,也就是说这些事务在 MGR 集群中,回放的时候可以以并行的方式回放。

MGR 中,使用的正是 WriteSet 技术检测不同事务之间是否存在写冲突,并重规划了事务的并行回放,这一技术在 8.0 中被移到了 Binlog 生成阶段,并采用到了主从复制的架构中。

四、MySQL 8.0 中的并行复制

说了这么多,终于讲到了 MySQL 8.0,通过以上描述,读者应该对 MySQL 8.0 中并行复制的优化的原理有了一个大致的轮廓。通过基于 WriteSet 的冲突检测,在主机上产生 Binlog 的时候,不再基于组提交,而是基于事务本身的更新冲突来确定并行关系。

1、相关的 MySQL 参数

在 MySQL 8.0 中,该版本引入了参数 binlog_transaction_depandency_tracking 用于控制如何决定事务的依赖关系。

该值有三个选项:

默认的 COMMIT_ORDERE 表示继续使用 5.7 中的基于组提交的方式决定事务的依赖关系;

WRITESET 表示使用写集合来决定事务的依赖关系;

还有一个选项 WRITESET_SESSION 表示使用 WriteSet 来决定事务的依赖关系,但是同一个 Session 内的事务不会有相同的 last_committed 值。

在代码实现上,MySQL 采用一个 vector uint64 的变量存储已经提交的事务的 HASH 值,所有已经提交的事务的所修改的主键和非空的 UniqueKey 的值经过 HASH 后与该 vector 中的值对比,由此来判断当前提交的事务是否与已经提交的事务更新了同一行,并以此确定依赖关系。该向量的大小由参数 binlog_transaction_dependency_history_size 控制,取值范围为 1 -1000000,初始默认值为 25000。

同时参数 transaction_write_set_extraction 控制检测事务依赖关系时采用的 HASH 算法有三个取值 OFF|XXHASH64|MURMUR32,如 binlog_transaction_depandency_tracking 取值为 WRITESET 或 WRITESET_SESSION,那么该值取值不能为 OFF,且不能变更。

2、WriteSet 依赖检测条件

WriteSet 是通过检测两个事务是否更新了相同的记录来判断事务能否并行回放的,因此需要在运行时保存已经提交的事务信息以记录历史事务更新了哪些行。记录历史事务的参数为 binlog_transaction_dependency_history_size。该值越大可以记录更多的已经提交的事务信息,不过需要注意的是,这个值并非指事务大小,而是指追踪的事务更新信息的数量。在开启了 WRITESET 或 WRITESET_SESSION 后,MySQL 按以下的方式标识并记录事务的更新。

如果事务当前更新的行有主键(Primary Key),则将 HASH(DB 名、TABLE 名、KEY 名称、KEY_VALUE1、KEY_VALUE2……)加入到当前事务的 vector write_set 中。

如果事务当前更新的行有非空的唯一键(Unique Key Not NULL),同样将 HASH(DB 名、TABLE 名、KEY 名、KEY_VALUE1)……加入到当前事务的 write_set 中。

如果事务更新的行有外键约束 (FOREIGN KEY) 且不为空,则将该外键信息与 VALUE 的 HASH 加到当前事务的 write_set 中;如果事务当前更新的表的主键是其它某个表的外键,则设置当前事务 has_related_foreign_key = true;如果事务更新了某一行且没有任何数据被加入到 write_set 中,则标记当前事务 has_missing_key = true。

在执行冲突检测的时候,先会检查 has_related_foreign_key 和 has_missing_key,如果为 true,则退到 COMMIT_ORDER 模式;否则,会依照事务的 write_set 中的 HASH 值与已提交的事务的 write_set 进行比对。

如果没有冲突,则当前事务与最后一个已提交的事务共享相同的 last_commited,否则将从全局已提交的 write_set 中删除那个冲突的事务之前提交的所有 write_set,并退化到 COMMIT_ORDER 计算 last_committed。

在每一次计算完事务的 last_committed 值以后,需要去检测当前全局已经提交的事务的 write_set 是否已经超过了 binlog_transaction_dependency_history_size 设置的值,如果超过,则清空已提交事务的全局 write_set。

从检测条件上看,该特性依赖于主键和唯一索引,如果事务涉及的表中没有主键且没有唯一非空索引,那么将无法从此特性中获得性能的提升。除此之外,还需要将 Binlog 格式设置为 Row 格式。

3、性能提升

MySQL High Availability 对开启了 WriteSet 的复制性能做了测试,这里直接将测试结果搬运过来,有兴趣的可以直接访问原博客。

测试时通过 Sysbench 先在主机上执行 100W 条事务,然后开启 Slave 的复制线程,测试环境在 Xeon E5-2699-V3 16 核主机上执行,以下是测试结果:

 

可以看到,在客户端线程比较少的时候 WRITESET 具有最好的性能,在只有一个连接时 WRITESET_SESSION 和 COMMIT_ORDER 差别不大。

感谢各位的阅读!关于“MySQL 8.0 主从复制模型的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-26发表,共计5627字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)