MySQL主从架构已经被广泛应用,保障主从复制关系的稳定性是大家一直关注的焦点。MySQL 5.6针对主从复制稳定性提供了新特性:slave支持crash-safe。该功能可以解决之前版本中系统异常断电可能导致relay_log.info位点信息不准确的问题。本文将从原理,参数等几个方面对该特性进行介绍。
在了解slave crash-safe 之前,我们先分析MySQL 5.6之前的版本出现slave crash-unsafe 的原因。我们知道在一套主从结构体系中,slave包含两个线程:即IO thread和SQL thread。两个线程的执行进度(偏移量)都保存在文件中。
IO thread负责从master拉取binlog文件并保存到本地的relay-log 文件中。 SQL thread负责执行重复sql,执行relay-log记录的日志。
crash-unsafe情况下 SQL_thread 的 的工作模式:
START TRANSACTION;
Statement 1
...
Statement N
COMMIT;
Update replication info files (master.info, relay_log.info)
IO thread的执行状态信息保存在master.info 文件, SQL thread的执行状态信息保存在 relay-log.info 文件。slave 运行正常的情况下,记录位点没有问题。但是每当系统发生crash,存储的偏移量可能是不准确的(需要注意的是这些文件被修改后不是同步写入磁盘的)。因为应用binlog和更新位点信息到文件不是原子操作,而是两个独立的步骤。比如 SQL thread已经应用relay-log.01的4个事务
trx1(pos:10)
trx2(pos:20)
trx3(pos:30)
trx4(pos:40)
但是 SQL thread 更新位点(relay-log.01,30)到relay-log.info 文件中,slave实例重启的时候 sql thread 会重复执行事务 trx4,于是乎,大家就看到比较常见的复制报错 error 1062 ,error 1032 。
通过上面的分析,我们知道slave crash-unsafe的原因在于应用binlog和更新文件的非原子性。MySQL 5.6版本通过将更新位点信息存放到表中,并且和正常的事务一起执行,进而保障apply binlog的事务和更新relay info信息到slave_relay_log_info 的原子性.
就是把SQL thread执行事务和更新 mysql.slave_replay_log_info 的语句合并为同一个事务,由MySQL系统来保障事务的原子性。我们可以通过伪代码来模拟 crash-safe 的原理:crash-safe情况下 SQL_thread 的工作模式
START TRANSACTION;
Statement 1
...
Statement N
Update replication info
COMMIT
一图胜千言:
绿色的代表实际业务的事务,蓝色的是开启MySQL执行的更新slave_replay_log_info 相关位点信息的sql ,然后将这两个sql合并在一个事务中执行,利用MySQL事务机制和InnoDB表保障原子性。不会出现应用binlog 和更新位点信息两个动作割裂导致不一致的问题。
通过设置 relay_log_recovery = ON,slave 遇到异常crash,然后重启的时候,系统会删除现有的relay log,然后IO thread会从mysql.slave_replay_log_info 记录的位点信息重新拉取主库的binlog。MySQL如此设计的出发点是:
一图胜千言:
蓝色的update语句代表已经执行并提交的事务,绿色的delete 语句表示正在执行的sql,还未提交。此时slave_replay_log_info表记录的relay log info是update语句结束,delete语句开始之前的位点 (relay_log.01,100) 。如果遇到系统crash,slave实例重启之后,会删除已经有的relaylog,并且IO thread会从(relay_log.01,100)对应的master binlog位点重新拉取主库的binlog,SQL thread也会从这个位点开始应用binlog。
和基于位点的复制不同,GTID 模式下使用新的复制协议 COM_BINLOG_DUMP_GTID 进行复制。举个?
实例a的事务集合set_a, 实例b的事务集合set_b ,设置b为a的从库的时候,其中的binlog协议伪算法如下:
GTID 模式下,slave crash-safe 运行机制
蓝色ABC:3 表示已经执行并提交的事务,绿色ABC:4表示正在执行的事务,此时slave crash,实例记录的gtid_executed=ABC:1-3,系统重启relay_log被删除。slave 将 UNION(@@global.gtid_executed, null) 的计算结果也即是gtid_executed=ABC:1-3发送到主库,主库会将ABC:3以后的binlog传送给slave继续执行。
注意 从新的复制协议中slave重启时是基于binlog中的GTID信息进行复制的,并不依赖于mysql.slave_replay_log_info。为了保障binlog及时落盘slave要设置 双1模式 sync_binlog = 1和innodb_flush_log_at_trx_commit = 1
通过配置两个如下两个参数开启该特性。
relay_log_info_repository = TABLE
relay_log_recovery = ON
看到这里是不是有疑问为什么没有master.info 相关的参数配置?
其实开启slave的crash-safe之后,slave重启的时候会自动清空之前的relay-log,IO thread从mysql.slave_relay_log_info表中记录的位点开始拉取数据,而不是依赖slave_master_info表相关数据。
注意: 如果是MySQL 5.6.5 或者更早期。slave_master_info 和 slave_relay_log_info 表默认使用MyISAM 引擎。所以还得修改成innodb,如下:
ALTER TABLE mysql.slave_master_info ENGINE=InnoDB; ALTER TABLE mysql.slave_relay_log_info ENGINE=InnoDB;
每个硬币都有它的两面性。开启crash-safe会带来哪些潜在的问题?
1 重启slave,重新拉取relay-log,一主多从的集群会给主库带来IO和带宽压力。
2 主库不可用,或者binlog被删除了,slave找不到所需要的binlog。
[1] 图片来自 https://hackmongo.com/post/crash-safe-mysql-replication-a-visual-guide/
[2] http://dev.mysql.com/doc/refman/5.7/en/replication-solutions-unexpected-slave-halt.html
[3] http://dev.mysql.com/doc/refman/5.7/en/replication-solutions-unexpected-slave-halt.html