当前位置: 首页 > 科技观察

MySQL8.0新特性:彻底解决困扰运维的复制延迟问题,你信吗?

时间:2023-03-17 18:42:58 科技观察

MySQL8.0可以说是MySQL发展史上里程碑式的版本,包含了数次重大更新。目前,正式版已经发布。这里我们将介绍8.0版本引入的一个重要的新特性——基于WriteSet的并行复制方案,号称彻底解决困扰MySQL运维人员多年的复制延迟问题。说到并行复制,这里简单回顾一下MySQL复制在各个版本中的演进,以帮助理解8.0版本中并行复制MTS的优化。一、MySQL主从复制模型一切从MySQL主从复制模型说起。下图是最经典的MySQL主从复制模型架构图:MySQL复制模型MySQL的主从架构依赖于MySQL的Binlog功能,MasterBinlog在节点上生成并写入Binlog文件。Slave节点上启动了两个线程:一个IO线程从MySQL中获取Binlog日志,写入本地RelayLog日志;另一个SQL线程不断从RelayLog日志中读取日志,并解析执行。在从机上添加几个文件的顺序读写操作,可以保证在宿主机上执行的所有SQL语句在从机上执行的完全一样。Replicationdelay是指Master执行完成后,到Slave上执行事务需要多长时间。由于Binlog文件和RelayLog文件的读写都是顺序操作,在生产环境中,Slave上的IO线程很少会在Binlog文件的dump操作中产生延迟。其实从MySQL5.5开始,MySQL官方就提供了半同步复制插件。每笔交易的Binlog都需要保证在提交前传输到Slave并写入RelayLog。这种架构提供了master和slave之间的数据完整性,保证master出现故障后,slave可以拥有一份完整的数据副本。因此,复制延迟通常发生在SQL线程执行期间。从架构图中可以看出,在最早的主从复制模型中,只有一个线程负责执行Relaylog,也就是说对master的所有操作都在slave上串行回放。这就带来了一个问题。如果master的写入压力比较大,slave的播放速度可能跟不上master。(另外,MySQL的架构决定了Binlog只有在Commit阶段才会写入Binlog文件并dump到从机,这也导致了主从事务的执行延迟,这个问题在大事务。不过这个问题不在本文讨论范围。)既然主从延迟的问题是RelayLog的单线程播放太慢,那么降低主从延迟的解决方案自然是增加并行度在从机上播放RelayLog。二、5.7中的并行复制1、Schema级别的并行复制。MySQL官方在5.6中引入了比较简单的并行复制方案。如果开启并行回放功能,会启动多个WorkThreads,原本负责回放的SQLThread会转变为Coordinator角色,负责判断事务是否可以并行执行,分发给WorkThreads。如果事务属于不同的schema,不是DDL语句,没有跨schema的操作,那么可以并行回放。否则,需要等待所有工作线程执行完毕,才能执行当前日志中的内容。这种并行播放是在Schema层面并行的。如果实例上有多个Schemas,它将从中受益。如果实例上只有一个Schema,那么事务不会并行回放,由于分布式操作较多,效率会略有下降。.在实际应用中,单库多表是比较常见的情况。2、基于GroupCommit的并行复制虽然5.6中的并行复制在大部分应用场景下并没有大幅提升回放速度,但是这个架构成为了后续MySQL并行复制的基础——即在Slave上并行回放RelayLog,SQL线程负责判断是否可以并行播放,分配给Work线程进行播放。5.6引入GroupCommit技术是为了解决提交事务时需要fsync导致并发不足的问题。简单的说,调用fsync是因为事务提交时,Binlog必须写入磁盘。这是一个相对昂贵的操作。实际上,Binlog文件是串行写入的,大大降低了事务提交的并发度。5.6中使用的GroupCommit技术将一个事务的提交阶段分为三个阶段:Flush、Sync和Commit。每个阶段维护一个队列,队列中的第一个线程负责执行这一步,实际上达到了将一批交易的Binlog一次性同步到磁盘的目的,这样一批交易在第同一时间称为同一组交易。GroupCommit虽然是一种并行提交技术,却出乎意料地解决了从机并行回放事务的一个难题——即如何确定哪些事务可以并行回放。如果同时提交了一批事务,那么这些事务肯定是没有互斥锁的,在执行过程中不会相互依赖,所以这些事务必须并行回放。因此MySQL5.7引入了一种新的并行播放类型,由参数slave_parallel_type决定。DATABASE的默认值将在5.6版本中使用SCHEMA级别的并行播放。如果设置为LOGICAL_LOCK,它将使用基于GroupCommit的并行播放。事务将在从站上并行重播。为了标记事务所属组,MySQL5.7版本在生成Binlog日志时,会在BinlogEvent中记录两个特殊的值,last_committed和sequence_number,其中last_committed是指上一个事务提交时提交的编号transactionwascommitted,sequence_number是事务提交的序号,在一个Binlog文件中单调递增。如果两个事务的last_committed值一致,则两个事务在一个组中提交。root@localhost:~#mysqlbinlogmysql-bin.0000006|greplast_committed#15052014:23:11serverid88end_log_pos259CRC320x4ead9ad6GTIDlast_committed=0sequence_number=1#15052014:23:11serverid88end_log_pos1483CRC320xdf94bc85GTIDlast_committed=0sequence_number=2#15052014:23:11serverid88end_log_pos2708CRC320x0914697bGTIDlast_committed=0sequence_number=3#15052014:23:11serverid88end_log_pos3934CRC320xd9cb4a43GTIDlast_committed=0sequence_number=4#15052014:23:11serverid88end_log_pos5159CRC320x06a6f531GTIDlast_committed=0sequence_number=5#15052014:23:11serverid88end_log_pos6386CRC320xd6cae930GTIDlast_committed=0sequence_number=6#15052014:23:11serverid88end_log_pos7610CRC320xa1ea531cGTIDlast_committed=6sequence_number=7#15052014:23:11serverid88end_log_pos8834CRC320x96864e6bGTIDlast_committed=6sequence_number=8#15052014:23:11serverid88end_log_pos10057CRC320x2de1ae55GTIDlast_committed=6sequence_number=9#15052014:23:11serverid88end_log_pos11280CRC320x5eb13091GTIDlast_committed=6sequence_number=10#15052014:23:11serverid88end_log_pos12504CRC320x16721011GTIDlast_committed=6sequence_number=11#15052014:23:11serverid88end_log_pos13727CRC320xe2210ab6GTIDlast_committed=6sequence_number=12#15052014:23:11serverid88end_log_pos14952CRC320xf41181d3GTIDlast_committed=12sequence_number=13如上binlog文件中,sequence_number1-6的事务last_committed都是0,所以它们属于同一个组,可以在从机上并行播放。7-12的last_committed都是6,属于同一组,所以可以并行回放。5.7引入的Logical_Lock,大大改善了主机的并发压力。在从机播放速度的情况下,基本实现了在主机上如何提交,在从机上如何播放。3、虽然MySQLMGR中的WriteSet是这样,但是在5.7中,基于逻辑时钟Logical_Clock的并行复制还是有一些不尽如人意的地方。如果压力不高,那么你就享受不到并行复制带来的好处。5.7中引入了binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count两个参数,通过让Binlog在执行fsync之前等待一小段时间来提高Master上groupcommit的速率。但无论如何,slave上并行播放的速度取决于master上的并行提交。MySQL8.0引入了一种新的机制来确定事务是否可以并行回放。通过检测事务运行过程中是否存在写冲突,确定从机上的播放顺序,使得从机上的并发度不再依赖于宿主机。事实上,这一机制在MySQL5.7.20版本中已经悄然应用。5.7.20版本引入了一个重要特性:GroupReplication,通过Paxso协议将binlog分发到多个MySQL节点,这样一个事务必须在集群中的大多数节点(N/2+1)上提交成功后才能提交.MySQLMRG为了支持多主写,在Binlog分发节点完成后,通过一个Certify阶段来判断Binlog中的事务是否写入RelayLog。在这个过程中,Certify阶段使用WriteSet方法来验证事务之间是否存在冲突。同时,在写入RelayLog时,会将非冲突事务的last_committed值设置为相同的值。比如在5.7.20中,进行如下操作:>--createagroupreplicationcluster.>STOPGROUP_REPLICATION;STARTGROUP_REPLICATION;QueryOK,0rowsaffected(9.10sec)>--Allthenextcommandsontheprimarymemberofthegroup:>CREATEDATABASEtest_ws_mgr;QueryOK,1rowaffected(0.01sec)>CREATETABLEtest_ws_mgr.test(idintprimarykeyauto_increment,strvarchar(64)notnull);QueryOK,1rowaffected(0`TOsecrtest)_INSERTINmg)VALUES("a");QueryOK,1rowaffected(0.01sec)>INSERTINTTOtest_ws_mgr.test(`str`)VALUES("b");QueryOK,1rowaffected(0.01sec)>INSERTINTTOtest_ws_mgr.test(`str`)VALUES("c");QueryOK,1rowaffected(0.01sec)上面的代码在MGR集群中创建了一个数据库和一个InnoDB表,并插入了3条记录。此时在Primary节点上查询Binlog可能会得到如下结果:#mysqlbinlogmysql-bin.N|greplast_|sed-e's/serverid.*last/[...]last/'-e's/.rbr_only。*/[...]/'#18010619:31:59[...]last_committed=0sequence_number=1[...]--CREATEDB#18010619:32:02[...]last_committed=1sequence_number=2[...]--CREATETB#18010619:32:05[...]last_committed=2sequence_number=3[...]--IN??SERTa#18010619:32:08[...]last_committed=3sequence_number=4[...]--INSERTb#18010619:32:11[...]last_committed=4sequence_number=5[...]--IN??SERTc可以看到由于是在一个Session中,所以这些操作的last_committed顺序不同,下正常情况下,这些BinlogEvents也应该在slave上串行回放。我们看一下MGR集群中RelayLog的情况:#mysqlbinlogmysql-relay.N|grep-elast_|sed-e's/serverid.*last/[...]last/'-e's/.rbr_only.*/[...]/'#18010619:31:36[...]last_committed=0sequence_number=0[...]#18010619:31:36[...]last_committed=1sequence_number=2[...]--CREATEDB#18010619:31:36[...]last_committed=2sequence_number=3[...]--CREATETB#18010619:31:36[...]last_committed=3sequence_number=4[...]--IN??SERTa#18010619:31:36[...]last_committed=3sequence_number=5[...]--IN??SERTb#18010619:31:36[...]last_committed=3sequence_number=6[...]--IN??SERTc有趣的是,在Secondary节点的RelayLog中,这些事务具有相同的last_committed值,这意味着这些事务可以在MGR集群中并行回放。在MGR中,是WriteSet技术检测不同事务之间是否存在写入冲突,重新规划事务的并行回放。该技术在8.0被移至Binlog生成阶段,并在主从复制架构中采用。4、MySQL8.0中的并行复制说了这么多,终于要说说MySQL8.0了。通过以上描述,读者应该对MySQL8.0中并行复制的优化原理有了一个大概的了解。通过基于WriteSet的冲突检测,在主机上产生Binlog时,是根据事务本身的更新冲突来判断并行关系,而不是根据groupcommit来判断。1.MySQL相关参数在MySQL8.0中,该版本引入参数binlog_transaction_dependency_tracking来控制如何判断事务依赖关系。这个值有3个选项:默认的COMMIT_ORDERE表示继续使用5.7中的groupcommit方式来判断事务的依赖关系;WRITESET表示使用写集来判断事务的依赖关系;还有一个选项WRITESET_SESSION表示使用WriteSet来确定事务Dependency的依赖关系,但是同一个Session内的事务不会有相同的last_committed值。代码实现上,MySQL使用一个vector变量来存储提交事务的HASH值,将所有提交事务修改后的主键和非空UniqueKey值与HASH后vector中的值进行比较,所以判断当前提交的事务是否与提交的事务更新同一行,判断依赖关系。这个vector的大小由参数binlog_transaction_dependency_history_size控制,取值范围1-1000000,初始默认值为25000。同时参数transaction_write_set_extraction控制检测事务依赖时使用的HASH算法。共有三个值:OFF|XXHASH64|MURMUR32。如果binlog_transaction_dependency_tracking的值为WRITESET或WRITESET_SESSION,则该值不能为OFF,不能更改。2.WriteSet取决于检测条件。WriteSet通过检测两个事务是否更新了同一条记录来判断事务是否可以并行回放。因此,需要在运行时保存提交的事务信息,记录哪些行被历史事务更新过。记录历史事务的参数为binlog_transaction_dependency_history_size。该值越大,可以记录的提交事务信息越多,但需要注意的是,这个值不是指事务大小,而是指跟踪到的事务更新信息的数量。启用WRITESET或WRITESET_SESSION后,MySQL通过以下方式识别和记录事务更新。如果事务当前更新的行有主键(PrimaryKey),则将HASH(DB名、TABLE名、KEY名、KEY_VALUE1、KEY_VALUE2...)添加到当前事务的向量write_set中。如果事务当前更新的行有非空唯一键(UniqueKeyNotNULL),还要在当前事务的write_set中加入HASH(DB名,TABLE名,KEY名,KEY_VALUE1)...。如果事务更新的行有外键约束(FOREIGNKEY)且不为空,则将外键信息和VALUE的HASH加入当前事务的write_set;如果事务当前更新的表的主键是其他表的外键,则设置当前事务has_related_foreign_key=true;如果事务更新一行并且没有数据添加到write_set,则标记当前事务has_missing_key=true。在进行冲突检测时,会先检查has_related_foreign_key和has_missing_key,如果为真,则返回COMMIT_ORDER模式;否则,它会将事务的write_set中的HASH值与已提交的事务的write_set进行比较。如果没有冲突,则当前事务与最后一个提交的事务共享同一个last_commited,否则所有在该冲突事务之前提交的write_set从全局committedwrite_set中移除,并退化为COMMIT_ORDER来计算last_committed。每次计算完事务的last_committed值后,需要检测当前全局提交的事务的write_set是否超过了binlog_transaction_dependency_history_size设置的值,如果超过则清空提交事务的全局write_set。从检测条件来看,这个特征依赖于主键和唯一索引。如果事务涉及的表没有主键,也没有唯一非空索引,那么就得不到该特性的性能提升。另外,还需要将Binlog格式设置为Row格式。3.性能改进MySQLHighAvailability测试了启用WriteSet的复制性能。测试结果直接传到这里。有兴趣的可以直接访问原博客。测试时,通过Sysbench在host上执行100万次事务,然后开启slave的复制线程。测试环境在XeonE5-2699-V316核主机上执行。下面是测试结果:可以看到,在客户端线程数较少的情况下,WRITESET性能最佳时,只有一个连接时WRITESET_SESSION和COMMIT_ORDER没有太大区别。五、结论从MySQLHighAvailability的测试中可以看出,开启基于WriteSet的事务依赖后,RelayLog在Slave上的播放速度有明显提升。Slave上的RelayLog播放速度将不再依赖于Master上提交的并行度,从而使Slave发挥其最佳的吞吐能力。当Slave上的复制停止一段时间再恢复复制时,这个特性尤其有效。这个特性使得Slave有可能比Master有更大的吞吐量。同时,在保证事务依赖的情况下,可以在Slave上生成Master上没有生成的commit场景。事务提交的顺序可能发生在Slave上。改变。虽然在5.7的并行复制中可能会出现这种情况,但由于Slave上的并发能力更高,这种情况在8.0中会更加常见。通常情况下,这不是什么大问题,但是如果你在slave上基于Binlog做增量备份,你可能需要保证slave上的commit顺序和master上的一致。这种情况下,可以开启slave_preserve_commit_order,这是一个5.7引入的参数,可以保证SlaveCommit上的并行回放线程按照写入RelayLog的顺序进行。参考http://jfg-mysql.blogspot.jp/2018/01/an-update-on-write-set-parallel-replication-bug-fix-in-mysql-8-0.htmlhttp://jfg-mysql.blogspot.jp/2018/01/write-set-in-mysql-5-7-group-replication.htmlhttps://mysqlhighavailability.com/improving-the-parallel-applier-with-writeset-based-依赖-跟踪/