本文是关于MySQL 8.0主从复制模型的示例分析。我觉得边肖很实用,就和大家分享一下作为参考。让我们跟着边肖看一看。
一、MySQL主从复制模型
一切都从MySQL的主从复制模式开始。下图是最经典的MySQL主从复制模型架构图:
MySQL复制模型
MySQL的主从架构依赖于MySQL Binlog函数。Binlog在主节点上生成,并写入Binlog文件。
在Slave节点上启动两个线程:一个IO线程,从MySQL中取出Binlog日志,写入本地RelayLog日志;另一个SQL线程,从RelayLog日志中连续读取日志,并解析和执行它。通过在主机和从机上增加几个顺序的文件读写操作,可以保证在主机上执行的所有SQL语句在从机上恰好执行一次。
复制延迟是指在主机执行完成后,在从机上执行一个事务需要多长时间。
因为读写Binlog文件和RelayLog文件是顺序操作,所以在生产环境中,从机上IO线程对Binlog文件的Dump操作很少会造成延迟。事实上,从MySQL 5.5开始,MySQL正式提供了半同步复制插件,每个事务的Binlog都要传到Slave并写入RelayLog后才能提交。这种架构提供了主设备和从设备之间的数据完整性,并确保从设备在主机出现故障后能够拥有完整的数据拷贝。因此,复制延迟通常发生在执行SQL线程的过程中。
从架构图可以看出,在最早的主从复制模型中,只有一个线程负责执行Relaylog,也就是说,主机上的所有操作都在从机上串行回放。这就带来了一个问题。如果母片上的写压力比较大,那么母片的播放速度很可能跟不上母片。(此外,MySQL架构决定了Binlog只会在Commit阶段写入Binlog文件并Dump给从机,这也导致主从事务的执行延迟,这在大事务中尤为明显,但不在本文讨论范围之内。)
由于主从延迟的问题是RelayLog的单线程播放太慢,所以降低主从延迟的解决方案自然是提高RelayLog从机播放的并行度。
二、5.7中的并行复制
1.模式级别的并行复制
MySQL在5.6中正式引入了一个相对简单并行复制方案,其架构如下:
(图片来自姜承尧老师的博客)
红框是平行播放的关键。如果在5.6中开启了并行回放的功能,会启动多个工作线程,将原来负责回放的SQLThread转换为Coordinator角色,负责判断事务是否可以并行执行,并分配给工作线程。
如果事务属于不同的模式,并且不是DDL语句,并且没有跨模式操作,那么它们可以并行回放,否则,当前日志中的内容需要在所有Worker线程执行后执行。
这种并行回放是模式级并行。如果实例上有多个模式,它将会受益。如果实例上只有一个模式,事务将无法并行回放,并且由于更多的分布式操作,效率会略有降低。在实际应用中,单个数据库和多个表是比较常见的。
2.基于组提交的并行复制
虽然5.6中的并行复制在大多数应用场景中并没有大幅提升播放速度,但这种架构后来成为了MySQL并行复制的基础。——是在Slave上并行播放RelayLog,SQL线程负责判断是否可以并行播放,分配给Work线程播放。
5.6引入了Group Commit技术,解决了提交事务时需要fsync导致并发不足的问题。简单来说,调用fsync是因为提交事务时必须将Binlog写入磁盘,这是一个相对昂贵的操作。在并发提交事务的情况下,每个事务获得一个日志锁,单独执行fsync,会导致事务以串行方式实际写入Binlog文件,大大降低了事务提交的并发性。
5.6采用的Group Commit技术将事务的提交阶段分为三个阶段:Flush、Sync和Commit。每个阶段维护一个队列,队列中的第一个线程负责执行这个步骤。事实上,一批事务的Binlog fsync可以一次传输到磁盘。这样一批同时提交的交易称为同组交易。
虽然Group 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主从复制模型的示例分析”这篇文章就分享到这里了,希望
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/73046.html