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 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
- #150520 14:23:11 server id 88 end_log_pos 8834 CRC32 0x96864e6b GTID last_committed=6 sequence_number=8
- #150520 14:23:11 server id 88 end_log_pos 10057 CRC32 0x2de1ae55 GTID last_committed=6 sequence_number=9
- #150520 14:23:11 server id 88 end_log_pos 11280 CRC32 0x5eb13091 GTID last_committed=6 sequence_number=10
- #150520 14:23:11 server id 88 end_log_pos 12504 CRC32 0x16721011 GTID last_committed=6 sequence_number=11
- #150520 14:23:11 server id 88 end_log_pos 13727 CRC32 0xe2210ab6 GTID last_committed=6 sequence_number=12
- #150520 14:23:11 server id 88 end_log_pos 14952 CRC32 0xf41181d3 GTID last_committed=12 sequence_number=13
- ...
可以发现较之原来的二进制日志内容多了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;
- ......
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;
- ...
可以看到在类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机制则完全无能为力了。 
并行复制配置与调优
master_info_repository
开启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
若将slave_parallel_workers设置为0,则MySQL 5.7退化为原单线程复制,但将slave_parallel_workers设置为1,则SQL线程功能转化为coordinator线程,但是只有1个worker线程进行回放,也是单线程复制。然而,这两种性能却又有一些的区别,因为多了一次coordinator线程的转发,因此slave_parallel_workers=1的性能反而比0还要差,在Inside君的测试下还有20%左右的性能下降,如下图所示:
这里其中引入了另一个问题,如果主机上的负载不大,那么组提交的效率就不高,很有可能发生每组提交的事务数量仅有1个,那么在从机的回放时,虽然开启了并行复制,但会出现性能反而比原先的单线程还要差的现象,即延迟反而增大了。聪明的小伙伴们,有想过对这个进行优化吗?
Enhanced Multi-Threaded Slave配置 
说了这么多,要开启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
并行复制监控
复制的监控依旧可以通过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)
总结
MySQL 5.7推出的Enhanced Multi-Threaded Slave解决了困扰MySQL长达数十年的复制延迟问题,再次提醒一些无知的PostgreSQL用户,不要停留在之前对于MySQL的印象,物理复制也不一定肯定比逻辑复制有优势,而MySQL 5.7的MTS已经完全可以解决延迟问题了。anyway,和Inside君一起见证划时代MySQL 5.7 GA版本的降临吧。
参考文献:
- https://dev.mysql.com/doc/refman/5.7/en/replication-options-slave.html
- http://dev.mysql.com/worklog/task/?id=5569
- http://dev.mysql.com/worklog/task/?id=4648
- http://dev.mysql.com/worklog/task/?id=6314
- http://www.innosql.net
MySQL 5.7并行复制时代的更多相关文章
- MySQL 5.7 并行复制实现原理与调优
MySQL 5.7并行复制时代 众所周知,MySQL的复制延迟是一直被诟病的问题之一,然而在Inside君之前的两篇博客中(1,2)中都已经提到了MySQL 5.7版本已经支持“真正”的并行复制功能, ...
- 官方:MySQL 5.7 并行复制实现原理与调优 | InsideMySQL(转载)
MySQL 5.7并行复制时代 众所周知,MySQL的复制延迟是一直被诟病的问题之一,然而在Inside君之前的两篇博客中(1,2)中都已经提到了MySQL 5.7版本已经支持“真正”的并行复制功能, ...
- mysql 5.6并行复制事件分发机制
并行复制相关线程 在MySQL 5.6并行复制中,当设置set global slave_parallel_workers=2时,共有4个复制相关的线程,如下: +----+------------- ...
- MySQL 5.7 并行复制
一.缘由: 某天看到主从复制延时的告警有点频繁,就想着是不是彻底可以解决一下. 一般主从复制,有三个线程参与,都是单线程:Binlog Dump(主) ----->IO Thread (从) - ...
- 谈谈MySQL的WriteSet并行复制
[历史背景] 岁月更迭中我已经从事MySQL-DBA这个工作三个年头,见证MySQL从“基本可用”,“边缘系统可以用MySQL”,“哦操!你怎么不用MySQL”; 正所谓!“一个数据库的境遇既取决于历 ...
- MySQL主从复制之并行复制说明
传统单线程复制说明 众所周知,MySQL在5.6版本之前,主从复制的从节点上有两个线程,分别是I/O线程和SQL线程. I/O线程负责接收二进制日志的Event写入Relay Log. SQL线程读取 ...
- MySQL 5.7基于组提交的并行复制
参考链接: http://mysql.taobao.org/monthly/2016/08/01/ https://www.kancloud.cn/thinkphp/mysql-parallel-ap ...
- MySQL并行复制(MTS)原理(完整版)
目录 MySQL 5.6并行复制架构 MySQL 5.7并行复制原理 Master 组提交(group commit) 支持并行复制的GTID slave LOGICAL_CLOCK(由order c ...
- Centos7.5部署MySQL5.7基于GTID主从复制+并行复制+半同步复制+读写分离(ProxySQL) 环境- 运维笔记 (完整版)
之前已经详细介绍了Mysql基于GTID主从复制的概念,原理和配置,下面整体记录下MySQL5.7基于GTID主从复制+并行复制+增强半同步复制+读写分离环境的实现过程,以便加深对mysql新特性GT ...
随机推荐
- ASP.NET快速开发框架不得不做的几个功能、高大上档次后台管理UI界面
俗话说磨刀不误砍柴工,确实,一早上花一个小时去磨刀一天下来肯定能多砍很多柴.我们做软件开发也是同样的道理,有套好开发框架在手里,开发也是事半功倍.那么一套MVC快速开发框架至少得具有哪些功能才能帮我们 ...
- TCP打开文件传输(客户端code)
#include <stdio.h>#include <stdlib.h>#include <arpa/inet.h>#include <sys/types. ...
- prim解决最小生成树问题
#include <iostream> #include <algorithm> #include <stdio.h> #include <math.h> ...
- CodeForces 680A&680B&680C&680D Round#356
昨天晚上实在是=_=困...(浪了一天)就没有去打Codeforces 中午醒来看看题,还不太难. A题:模拟(水题 3minAC) // by Sirius_Ren #include <cst ...
- RabbitMQ .NET消息队列使用入门(一)【简单示例】
首先下载安装包,我都环境是win7 64位: 去官网下载 otp_win64_19.0.exe 和rabbitmq-server-3.6.3.exe安装好 然后开始编程了: (1)创建生产者类: cl ...
- nginx单机1w并发设置
关闭keep-live,提高链接回收 keeplive_timeout 0; events{ worker_connections 1024; } more /proc/sys/net/core/so ...
- 揭开jQuery的面纱
简单地说,jQuery是一个优秀的JavaScript类库,也就是使用JavaScript面向对象的性质编写的一个JavaScript类的集合.jQuery究竟能为我们提供哪些功能呢?简单地说可以从七 ...
- 05-- C++ 类的静态成员详细讲解
C++ 类的静态成员详细讲解 在C++中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用.所以在所有对象中都可以共享它.使用静态成员变量实现多个对象之间的数据共享不 ...
- C# 遍历文本框
#region 文本框指定位置加入回车符 private void button1_Click(object sender, EventArgs e) { #region // 查询首字母位置 //s ...
- python PIL图像处理-图片上添加文字
首先需要安装库pillow cmd安装命令:pip install pillow 安装完后,编写脚本如下: from PIL import Image, ImageDraw, ImageFont de ...