MySQL 5.7并行复制时代

众所周知,MySQL的复制延迟是一直被诟病的问题之一,然而在Inside君之前的两篇博客中(1,2)中都已经提到了MySQL 5.7版本已经支持“真正”的并行复制功能,官方称为为enhanced multi-threaded slave(简称MTS),因此复制延迟问题已经得到了极大的改进,甚至在Inside君所在的网易电商应用中已经完全消除了之前延迟长达几小时的问题。然而,Inside君发现还是有很多小伙伴并不了解这个足以载入史册的“伟大”的特性,故作分享。总之,5.7版本后,复制延迟问题永不存在。

MySQL 5.6并行复制架构

诚然,MySQL 5.6版本也支持所谓的并行复制,但是其并行只是基于schema的,也就是基于库的。如果用户的MySQL数据库实例中存在多个schema,对于从机复制的速度的确可以有比较大的帮助。MySQL 5.6并行复制的架构如下所示:



mysql并行执行dml mysql5.7 并行复制_二进制日志

mysql 5.7 并行复制



在上图的红色框框部分就是实现并行复制的关键所在。在MySQL 5.6版本之前,Slave服务器上有两个线程I/O线程和SQL线程。I/O线程负责接收二进制日志(更准确的说是二进制日志的event),SQL线程进行回放二进制日志。如果在MySQL 5.6版本开启并行复制功能,那么SQL线程就变为了coordinator线程,coordinator线程主要负责以前两部分的内容:

  • 若判断可以并行执行,那么选择worker线程执行事务的二进制日志
  • 若判断不可以并行执行,如该操作是DDL,亦或者是事务跨schema操作,则等待所有的worker线程执行完成之后,再执行当前的日志

这意味着coordinator线程并不是仅将日志发送给worker线程,自己也可以回放日志,但是所有可以并行的操作交付由worker线程完成。coordinator线程与worker是典型的生产者与消费者模型。

上述机制实现了基于schema的并行复制存在两个问题,首先是crash safe功能不好做,因为可能之后执行的事务由于并行复制的关系先完成执行,那么当发生crash的时候,这部分的处理逻辑是比较复杂的。从代码上看,5.6这里引入了Low-Water-Mark标记来解决该问题,从设计上看(WL#5569),其是希望借助于日志的幂等性来解决该问题,不过5.6的二进制日志回放还不能实现幂等性。另一个最为关键的问题是这样设计的并行复制效果并不高,如果用户实例仅有一个库,那么就无法实现并行回放,甚至性能会比原来的单线程更差。而单库多表是比多库多表更为常见的一种情形。

MySQL 5.7并行复制原理

MySQL 5.7基于组提交的并行复制

MySQL 5.7才可称为真正的并行复制,这其中最为主要的原因就是slave服务器的回放与主机是一致的即master服务器上是怎么并行执行的slave上就怎样进行并行回放。不再有库的并行复制限制,对于二进制日志格式也无特殊的要求(基于库的并行复制也没有要求)。

从MySQL官方来看,其并行复制的原本计划是支持表级的并行复制和行级的并行复制,行级的并行复制通过解析ROW格式的二进制日志的方式来完成,WL#4648。但是最终出现给小伙伴的确是在开发计划中称为:MTS: Prepared transactions slave parallel applier,可见:WL#6314。该并行复制的思想最早是由MariaDB的Kristain提出,并已在MariaDB 10中出现,相信很多选择MariaDB的小伙伴最为看重的功能之一就是并行复制。

MySQL 5.7并行复制的思想简单易懂,一言以蔽之:一个组提交的事务都是可以并行回放,因为这些事务都已进入到事务的prepare阶段,则说明事务之间没有任何冲突(否则就不可能提交)。

为了兼容MySQL 5.6基于库的并行复制,5.7引入了新的变量slave-parallel-type,其可以配置的值有:

  • DATABASE:默认值,基于库的并行复制方式
  • LOGICAL_CLOCK:基于组提交的并行复制方式

支持并行复制的GTID

如何知道事务是否在一组中,又是一个问题,因为原版的MySQL并没有提供这样的信息。在MySQL 5.7版本中,其设计方式是将组提交的信息存放在GTID中。那么如果用户没有开启GTID功能,即将参数gtid_mode设置为OFF呢?故MySQL 5.7又引入了称之为Anonymous_Gtid的二进制日志event类型,如:






mysql          >           SHOW           BINLOG           EVENTS           in           'mysql-bin.000006'          ;


 +          --          --          --          --          --          --          --          --          --          +          --          --          -          +          --          --          --          --          --          --          --          --          +          --          --          --          --          --          -          +          --          --          --          --          --          --          -          +          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          -          +


 |           Log_name           |           Pos           |           Event_type           |           Server_id           |           End_log_pos           |           Info           |


 +          --          --          --          --          --          --          --          --          --          +          --          --          -          +          --          --          --          --          --          --          --          --          +          --          --          --          --          --          -          +          --          --          --          --          --          --          -          +          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          --          -          +


 |           mysql          -          bin          .          000006           |           4           |           Format_desc           |           88           |           123           |           Server           ver          :           5.7.7          -          rc          -          debug          -          log          ,           Binlog           ver          :           4           |


 |           mysql          -          bin          .          000006           |           123           |           Previous_gtids           |           88           |           194           |           f11232f7          -          ff07          -          11e4          -          8fbb          -          00ff55e152c6          :          1          -          2           |


 |           mysql          -          bin          .          000006           |           194           |           Anonymous_Gtid           |           88           |           259           |           SET           @          @          SESSION          .          GTID_NEXT          =           'ANONYMOUS'           |


 |           mysql          -          bin          .          000006           |           259           |           Query           |           88           |           330           |           BEGIN           |


 |           mysql          -          bin          .          000006           |           330           |           Table_map           |           88           |           373           |           table_id          :           108           (          aaa          .          t          )           |


 |           mysql          -          bin          .          000006           |           373           |           Write_rows           |           88           |           413           |           table_id          :           108           flags          :           STMT_END_F           |


 .          .          .          .          .



这意味着在MySQL 5.7版本中即使不开启GTID,每个事务开始前也是会存在一个Anonymous_Gtid,而这GTID中就存在着组提交的信息。

LOGICAL_CLOCK

然而,通过上述的SHOW BINLOG EVENTS,我们并没有发现有关组提交的任何信息。但是通过mysqlbinlog工具,用户就能发现组提交的内部信息:






root          @          localhost          :          ~          # mysqlbinlog mysql-bin.0000006 | grep last_committed


 #150520 14:23:11 server id 88 end_log_pos 259 CRC32 0x4ead9ad6 GTID last_committed=0 sequence_number=1


 #150520 14:23:11 server id 88 end_log_pos 1483 CRC32 0xdf94bc85 GTID last_committed=0 sequence_number=2


 #150520 14:23:11 server id 88 end_log_pos 2708 CRC32 0x0914697b GTID last_committed=0 sequence_number=3


 #150520 14:23:11 server id 88 end_log_pos 3934 CRC32 0xd9cb4a43 GTID last_committed=0 sequence_number=4


 #150520 14:23:11 server id 88 end_log_pos 5159 CRC32 0x06a6f531 GTID last_committed=0 sequence_number=5


 #150520 14:23:11 server id 88 end_log_pos 6386 CRC32 0xd6cae930 GTID last_committed=0 sequence_number=6


 #150520 14:23:11 server id 88 end_log_pos 7610 CRC32 0xa1ea531c GTID last_committed=6 sequence_number=7


 .          .          .



可以发现较之原来的二进制日志内容多了last_committed和sequence_number,last_committed表示事务提交的时候,上次事务提交的编号,如果事务具有相同的last_committed,表示这些事务都在一组内,可以进行并行的回放。例如上述last_committed为0的事务有6个,表示组提交时提交了6个事务,而这6个事务在从机是可以进行并行回放的。

上述的last_committed和sequence_number代表的就是所谓的LOGICAL_CLOCK。先来看源码中对于LOGICAL_CLOCK的定义: