众所周知,MySQL的复制延迟是一直被诟病的问题之一,然而在Inside君以前的两篇博客中(1,2)中都已经提到了MySQL 5.7版本已经支持“真正”的并行复制功能,官方称为为enhanced multi-threaded slave(简称MTS),所以复制延迟问题已经获得了极大的改进,甚至在Inside君所在的网易电商应用中已经彻底消除了以前延迟长达几小时的问题。 然而,Inside君发现仍是有不少小伙伴并不了解这个足以载入史册的“伟大”的特性,故做分享。总之,5.7版本后,复制延迟问题永不存在。mysql
诚然,MySQL 5.6版本也支持所谓的并行复制,可是其并行只是基于schema的,也就是基于库的。若是用户的MySQL数据库实例中存在多个schema,对于从机复制的速度的确能够有比较大的帮助。MySQL 5.6并行复制的架构以下所示:sql
mysql 5.7 并行复制数据库
在上图的红色框框部分就是实现并行复制的关键所在。在MySQL 5.6版本以前,Slave服务器上有两个线程I/O线程和SQL线程。I/O线程负责接收二进制日志(更准确的说是二进制日志的event),SQL线 程进行回放二进制日志。若是在MySQL 5.6版本开启并行复制功能,那么SQL线程就变为了coordinator线程,coordinator线程主要负责之前两部分的内容:服务器
若判断能够并行执行,那么选择worker线程执行事务的二进制日志架构
若判断不能够并行执行,如该操做是DDL,亦或者是事务跨schema操做,则等待全部的worker线程执行完成以后,再执行当前的日志app
这意味着coordinator线程并非仅将日志发送给worker线程,本身也能够回放日志,可是全部能够并行的操做交付由worker线程完成。coordinator线程与worker是典型的生产者与消费者模型。ide
上述机制实现了基于schema的并行复制存在两个问题,首先是crash safe功能很差作,由于可能以后执行的事务因为并行复制的关系先完成执行,那么当发生crash的时候,这部分的处理逻辑是比较复杂的。从代码上 看,5.6这里引入了Low-Water-Mark标记来解决该问题,从设计上看(WL#5569),其是但愿借助于日志的幂等性来解决该问题,不过 5.6的二进制日志回放还不能实现幂等性。另外一个最为关键的问题是这样设计的并行复制效果并不高,若是用户实例仅有一个库,那么就没法实现并行回放,甚至 性能会比原来的单线程更差。而单库多表是比多库多表更为常见的一种情形。工具
MySQL 5.7才可称为真正的并行复制,这其中最为主要的缘由就是slave服务器的回放与主机是一致的即master服务器上是怎么并行执行的slave上就怎 样进行并行回放。再也不有库的并行复制限制,对于二进制日志格式也无特殊的要求(基于库的并行复制也没有要求)。post
从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:基于组提交的并行复制方式
如何知道事务是否在一组中,又是一个问题,由于原版的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 | .....
1 2 3 4 5 6 7 8 9 10 11 |
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 ...
1 2 3 4 5 6 7 8 9 |
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的定义:
class Logical_clock { private: int64 state; /* Offset is subtracted from the actual "absolute time" value at logging a replication event. That is the event holds logical timestamps in the "relative" format. They are meaningful only in the context of the current binlog. The member is updated (incremented) per binary log rotation. */ int64 offset; ......
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Logical_clock { private: int64 state; /* Offset is subtracted from the actual "absolute time" value at logging a replication event. That is the event holds logical timestamps in the "relative" format. They are meaningful only in the context of the current binlog. The member is updated (incremented) per binary log rotation. */ int64 offset; ...... |
state是一个自增的值,offset在每次二进制日志发生rotate时更新,记录发生rotate时的state值。其实state和offset记录的是全局的计数值,而存在二进制日志中的仅是当前文件的相对值。使用LOGICAL_CLOCK的场景以下:
class MYSQL_BIN_LOG: public TC_LOG { ... public: /* Committed transactions timestamp */ Logical_clock max_committed_transaction; /* "Prepared" transactions timestamp */ Logical_clock transaction_counter; ...
1 2 3 4 5 6 7 8 9 |
class MYSQL_BIN_LOG: public TC_LOG { ... public: /* Committed transactions timestamp */ Logical_clock max_committed_transaction; /* "Prepared" transactions timestamp */ Logical_clock transaction_counter; ... |
能够看到在类MYSQL_BIN_LOG中定义了两个Logical_clock的变量:
max_c ommitted_transaction:记录上次组提交时的logical_clock,表明上述mysqlbinlog中的last_committed
transaction_counter:记录当前组提交中各事务的logcial_clock,表明上述mysqlbinlog中的sequence_number
下图显示了开启MTS后,slave服务器的QPS。测试的工具是sysbench的单表全update测试,测试结果显示在16个线程下的性能最 好,从机的QPS能够达到25000以上,进一步增长并行执行的线程至32并无带来更高的提高。而原单线程回放的QPS仅在4000左右,可见 MySQL 5.7 MTS带来的性能提高,而因为测试的是单表,因此MySQL 5.6的MTS机制则彻底无能为力了。
mysql 5.7 并行复制
开启MTS功能后,务必将参数master_info_repostitory设置为TABLE,这样性能能够有50%~80%的提高。这是由于并 行复制开启后对于元master.info这个文件的更新将会大幅提高,资源的竞争也会变大。在以前InnoSQL的版本中,添加了参数来控制刷新 master.info这个文件的频率,甚至能够不刷新这个文件。由于刷新这个文件是没有必要的,即根据master-info.log这个文件恢复自己 就是不可靠的。在MySQL 5.7中,Inside君推荐将master_info_repository设置为TABLE,来减少这部分的开销。
若将slave_parallel_workers设置为0,则MySQL 5.7退化为原单线程复制,但将slave_parallel_workers设置为1,则SQL线程功能转化为coordinator线程,可是只有1 个worker线程进行回放,也是单线程复制。然而,这两种性能却又有一些的区别,由于多了一次coordinator线程的转发,所以 slave_parallel_workers=1的性能反而比0还要差,在Inside君的测试下还有20%左右的性能降低,以下图所示:
mysql 5.7 并行复制
这里其中引入了另外一个问题,若是主机上的负载不大,那么组提交的效率就不高,颇有可能发生每组提交的事务数量仅有1个,那么在从机的回放时,虽然开启了并行复制,但会出现性能反而比原先的单线程还要差的现象,即延迟反而增大了。聪明的小伙伴们,有想过对这个进行优化吗?
说了这么多,要开启enhanced multi-threaded slave其实很简单,只需根据以下设置:
# slave slave-parallel-type=LOGICAL_CLOCK slave-parallel-workers=16 master_info_repository=TABLE relay_log_info_repository=TABLE relay_log_recovery=ON
1 2 3 4 5 6 |
# slave slave-parallel-type=LOGICAL_CLOCK slave-parallel-workers=16 master_info_repository=TABLE relay_log_info_repository=TABLE relay_log_recovery=ON |
复制的监控依旧能够经过SHOW SLAVE STATUS\G,可是MySQL 5.7在performance_schema架构下多了如下这些元数据表,用户能够更细力度的进行监控:
mysql> show tables like 'replication%'; +---------------------------------------------+ | Tables_in_performance_schema (replication%) | +---------------------------------------------+ | replication_applier_configuration | | replication_applier_status | | replication_applier_status_by_coordinator | | replication_applier_status_by_worker | | replication_connection_configuration | | replication_connection_status | | replication_group_member_stats | | replication_group_members | +---------------------------------------------+ 8 rows in set (0.00 sec)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
mysql> show tables like 'replication%'; +---------------------------------------------+ | Tables_in_performance_schema (replication%) | +---------------------------------------------+ | replication_applier_configuration | | replication_applier_status | | replication_applier_status_by_coordinator | | replication_applier_status_by_worker | | replication_connection_configuration | | replication_connection_status | | replication_group_member_stats | | replication_group_members | +---------------------------------------------+ 8 rows in set (0.00 sec) |
MySQL 5.7推出的Enhanced Multi-Threaded Slave解决了困扰MySQL长达数十年的复制延迟问题,再次提醒一些无知的PostgreSQL用户,不要停留在以前对于MySQL的印象,物理复制 也不必定确定比逻辑复制有优点,而MySQL 5.7的MTS已经彻底能够解决延迟问题了。anyway,和Inside君一块儿见证划时代MySQL 5.7 GA版本的降临吧。