最近测试环境的一个三节点的MySQL GR集群(单主)的一个从节点发生了宕机,重启恢复后,过了一会监控系统告警,刚刚宕机的这个节点退出了GR集群。原因是主库上对某条数据的修改复制到从库后,在这个从库上找不到这条数据(slave_exec_mode=STRICT),第一反应就是主库和从库出现了不一致。因为是测试环境,数据量不大,所以采取的方案是将从库重建(团队没有专门的DBA)。接下来特意查看了一下MySQL复制和从库宕机恢复这块的资料。

Crah Safe是MySQL 5.6新增的特性,主要是为了解决之前版本中因系统异常宕机而引起的SQL线程信息错误的问题。我们知道在作为从库角色的MySQL使用两个线程:

  • IO线程:负责把主库的二进制日志(bin log)复制到本地中继日志(relay log)
  • SQL线程:负责执行中继日志(relay log)中的SQL

这两个线程都需要记录当前日志的位置,之前的版本是记录到文件中,IO线程记录到master.info文件,SQL线程记录到relay-log.info文件。当从节点重启就可以根据relay-log.info中记录的位置继续同步。因为文件系统会自己控制缓存刷新,所以master.info文件和relay-log.info文件的写入位置在发生宕机的情况下可能是不准确的。MySQL提供了sync_master_infosync_relay_log_info来控制在完成配置的事务数后同步到磁盘(默认值在MySQL 5.7中都是10000),可以设置sync_master_info=1sync_relay_log_info=1即每个事务完成后都同步磁盘,但是这样对磁盘的消耗很大,即使都设置成1,实际还是会出现数据不一致,因为涉及到文件操作和数据库操作,有时无法实现原子性。

MySQL 5.6提供了Crash Safe Slave的特性,通过设置master_info_repository=TABLErelay_log_info_repository=TABLE将复制信息保存到表中,mysql.slave_master_info表和mysql.slave_relay_log_info表,这样就都是数据库操作了,可以将复制信息的更新放到事务中,确保数据一致。 再来看一下IO线程和SQL线程:

  • IO线程会在sync_master_info个事务后更新一次mysql.slave_master_info表,前面给出了sync_master_info的默认值是10000,也就是每提交10000个事务更新一次,这是不符合Crash Safe的,可以设置sync_master_info=1,但这会影响性能。
  • SQL线程会在一个事务中完成数据同步和对mysql.slave_relay_log_info表的更新

学习到这里,就找到了我们这次从库宕机出现数据不一致的原因了,我们配置了master_info_repository=TABLErelay_log_info_repository=TABLE,而没有配置sync_master_info,所以默认值是10000,因此这个从库不是Crash Safe的。处于性能的考虑,不能设置sync_master_info=1,实际上MySQL提供了一种更好的方式,即配置 relay_log_recovery = ON,当MySQL重启时,会从mysql.slave_relay_log_info恢复IO线程的最新位置,根据这个位置重新从主库获取bin log,这样就避免了由于未刷盘导致的relay log文件不完整造成的数据不一致。 当前相关的配置如下:

1gtid_mode=ON
2enforce_gtid_consistency=ON
3master_info_repository=TABLE
4relay_log_info_repository=TABLE
5relay_log_recovery = ON
6binlog_checksum=NONE
7log_slave_updates=ON

参考