深入浅出MySQL crash-safe

2019-04-09 11:20:35 浏览数 (1)

一 前言

MySQL主从架构已经被广泛应用,保障主从复制关系的稳定性是大家一直关注的焦点。MySQL 5.6针对主从复制稳定性提供了新特性:slave支持crash-safe。该功能可以解决之前版本中系统异常断电可能导致relay_log.info位点信息不准确的问题。本文将从原理,参数等几个方面对该特性进行介绍。

二 crash-unsafe

在了解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 的 的工作模式:

代码语言:javascript复制
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个事务

代码语言:javascript复制
 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 。

三 crash-safe 特性

3.1 保障apply log和更新位点信息操作的原子性

通过上面的分析,我们知道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 的工作模式

代码语言:javascript复制
START TRANSACTION;
  Statement 1
  ...
  Statement N
  Update replication info
COMMIT

一图胜千言:

绿色的代表实际业务的事务,蓝色的是开启MySQL执行的更新slave_replay_log_info 相关位点信息的sql ,然后将这两个sql合并在一个事务中执行,利用MySQL事务机制和InnoDB表保障原子性。不会出现应用binlog 和更新位点信息两个动作割裂导致不一致的问题。

3.2 crash 后的恢复动作

通过设置 relay_log_recovery = ON,slave 遇到异常crash,然后重启的时候,系统会删除现有的relay log,然后IO thread会从mysql.slave_replay_log_info 记录的位点信息重新拉取主库的binlog。MySQL如此设计的出发点是:

  1. SQL thread apply binlog的位点永远小于等于IO thread从主库拉取的位点。
  2. SQL thread记录的位点是已经执行并且提交的事务之后位点信息。

一图胜千言:

蓝色的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。

3.3 GTID 模式下的crash safe

和基于位点的复制不同,GTID 模式下使用新的复制协议 COM_BINLOG_DUMP_GTID 进行复制。举个?

实例a的事务集合set_a, 实例b的事务集合set_b ,设置b为a的从库的时候,其中的binlog协议伪算法如下:

  1. 实例b指向主库实例a, 基于主备协议建立主从关系
  2. 实例b将GTID信息发送给实例a UNION(@@global.gtid_executed, Retrieved_gtid_set - last_received_GTID)
  3. 实例a计算出set_b 与set_a的差集,也就是存在于set_a 但是不存在与set_b 的GTID集合,判断实例a本地的binlog是否包含了该差集所需要的所有binlog事务。 a 如果不包含,表示实例a已经把实例b需要的binlog删除了,直接返回报错。 b 如果确认全部包含 实例a从本地binlog文件里面,找到第一个不在set_b 的事务,发送给实例b
  4. 从这个事务开始,往后读文件,按顺序取binlog发送给实例b。

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

3.4 如何开启crash-safe 特性

通过配置两个如下两个参数开启该特性。

代码语言:javascript复制
 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;

3.5 相关参数
  1. 开启crash-safe之后,slave 重启之后,不再依赖master info相关的参数,所以这两个参数不做过多讨论。不过为了和relay log info存储一致,推荐存储maste-info到表里,sync_master_info 保持默认,设置为比较低的值,在写压力比较大的情况下,会有IO损耗。 master_info_repository =TABLE sync_master_info=0
  2. 开启crash-safe 必要参数 relay_log_info_repository = TABLE relay_log_recovery = 1 这2个不多做介绍了,前面已经将的非常透彻。
  3. relay log 相关 当relay_log_info_repository=file时, 更新位点信息的频率依赖于sync_relay_log_info = N (N>=0): a 当sync_relay_log_info=0时,MySQL依赖OS系统定期更新。 b 当sync_relay_log_info=N时(N>0), MySQL server 会在每执行N个事务之后调用fdatasync()刷relay-log.info 文件。 当relay_log_info_repository=table 如果mysql.slave_relay_log_info是 innodb 存储引擎,则每次事务更新,系统会自动忽略sync_relay_log_info的设置。 如果mysql.slave_relay_log_info是非事务存储引擎,则 a 当sync_relay_log_info=0时,不更新。 b 当sync_relay_log_info=N时(N>0), MySQL server 会在每执行N个事务之后调用fdatasync()刷relay-log.info 文件。 sync_relay_log 控制着relay-log的刷新策略,类似sync_binlog。不过这个参数在开启crash-safe特性之后没有什么实质的意义。建议保持该参数为默认值即可

四 其他问题

每个硬币都有它的两面性。开启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

0 人点赞