MySQL#复制 - crash-safe Replication - 上
Posted yangyidba
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL#复制 - crash-safe Replication - 上相关的知识,希望对你有一定的参考价值。
本篇文章要讨论的是复制环境下的crash-safe,换句话说的意思就是:保证无论在master还是slave发生异常crash拉起后,整个复制结构是支持ACID特性的,也意味着仅考虑支持事务的存储引擎(MyISAM场景不考虑)。本篇为上篇,内容为5.5/5.6,不包括MTS场景。5.7+ MTS场景见下篇。
主库配置
在讨论从库之前,首先,主库的不合理设置同样会在一些情况下造成从库发生复制故障,所以主库得需要是双1的。
非双1有很可能造成从库比主库多事务,所以从库异常是比较容易发生的事情。
复制中一致性保证影响因素
一句话简化看这个问题,就是保证复制过程中的两个线程:IO Thread、SQL Thread不掉链子。
IO Thread,用于接收主库binlog events,写入relay log。
SQL Thread,用于应用relay log到数据文件中。
对于从库节点,如果发生db crash或者宕机,IO Thread要知道从哪里继续拉取binlog,同时SQL Thread要知道从relay log中的哪个位置点开始继续应用日志。即恢复之前的复制进度,不能漏应用事务,也不能重复应用事务。(类似断点续传)
所以需要有一个地方能够存储更新进度:————
mysql 5.5
5.5是远古版本,当时:
IO Thread和SQL Thread分别往master.info、relay-log.info两个文件中更新元数据。对应的元数据信息被称作“master info”与“relay log info”。
但默认情况下,这俩文件更新频次是非常低的。
关于写master info和relay log info,有两个相关参数可以“尽量”保证。
sync_master_info
,指每经过sync_master_info个events,slave将master.info文件落盘。为0时则仅靠OS来控制。
sync_relay_log_info
,指每经过sync_relay_log_info个events,slave将relay-log.info文件落盘。为0时则仅靠OS来控制。
默认值为10000,很夸张。
如果将两个info参数设置为1,可保证每次接收或应用events之后,再将文件写入磁盘。
听起来很靠谱,但是这样搞会有两个问题:
1、性能差。
2、提交事务在先,更新复制信息到文件在后,极端情况下仍然可能出错。
上述更新过程如下:
1、apply relay log中的事务
2、再更新relay-log.info文件
复制相关的元数据信息出现问题会产生什么后果?举一个简单的故障例子:
如果异常crash后拉起,SQL Thread若读到了旧的位置(relay-log.info没及时更新),这样会重新apply某个提交过的事务,“恶名昭著”的1032、1062就来了。如果没有主键或唯一键的事务可能会更糟——没办法简单通过SHOW SLAVE STATUS去检查主从数据是否不一致了。这个场景好理解。
至于写relay log这个操作本身,也是难以保证极端情况下crash是没问题的,即便是将sync_relay_log设置为1也如此。
总结如下,因为
master.info的位置
relay-log.info的位置
relay log里的内容
三者均不100%可信(即便相关参数都设置成1)
所以,MySQL 5.5在复制环境中是不安全的。
MySQL 5.6
有个重大改进,将master info和relay log info写入了表里,也就是对应mysql.slave_master_info和mysql.slave_relay_log_info这俩张表,该表早期还是MyISAM,需要在5.6安装好后手动改为InnoDB。当然在某个5.6的小版本之后就默认为InnoDB了。
该特性对应了两个参数:
master_info_repository = TABLE | FILE
relay_log_info_repository = TABLE | FILE
而InnoDB就很舒服了,意味着IO Thread和SQL Thread更新对应的元数据信息时,是支持事务的,妙啊。
支持事务更新relay log info的过程就是:
START TRANSACTION;
apply log event;
UPDATE mysql.slave_relay_log_info
SET Master_log_pos = Exec_Master_Log_Pos,
Master_log_name = Relay_Master_Log_File,
Relay_log_name = Relay_Log_File,
Relay_log_pos = Relay_Log_Pos;
COMMIT;
简单想一下就可以发现,其实只需要保证relay log info不出错就行了,故只需要配置如下即可保证crash safe:
relay_log_info_repository = TABLE
relay_log_recovery = ON
补充,打开relay_log_recovery时,在重启后会发生什么呢?
IO Thread的位点会重新设置为SQL Thread的位点(以relay log info为准)
以SQL Thread的位置创建一个新的relay log
通过上述两个参数的设置,则可以保证SQL Thread每一次apply事务的时候,能及时更新自己的relay log info的信息,就足够了。在从库异常crash拉起后,只需要以relay log info覆盖到master info,然后让IO Thread重新拉取relay log即可。
so,master_info_repository存文件还是存表里,其实没所谓,不过设置为TABLE也是不错的选择,理由是强迫症或者为了统一称它们为“双TABLE”听起来很舒服。
MySQL 5.6 + GTID
走的协议不一样了,用的COM_BINLOG_DUMP_GTID,所以定位方式也不一样了。
gtid_mode = ON & MASTER_AUTO_POSITION = 0:
虽然真有DBA这样配,但手册里也没怎么提及这种配置,非主流不讨论。gtid_mode = ON & MASTER_AUTO_POSITION = 1:
和file & pos的方式一致。
另外还有一种配置如下:
log_slave_updates(5.6开gtid必须打开此参数)
log_bin(5.6开gtid必须打开此参数)
relay_log_recovery = ON
sync_binlog = 1
如果按上述配置,relay_log_info_repository是FILE或者TABLE也就无所谓了,因为auto-position不依赖relay log info了,而是通过Executed_gtid_set去判断自己和主库的差异事务。
细节可以阅读响应代码:
Rpl_slave.cc:request_dump()
Rpl_master.cc:com_binlog_dump_gtid()
但sync_binlog=1是必备的,需要binlog安全落地。因为GTID信息依赖binlog,从库可以通过自己的binlog获取自身完整的Executed_gtid_set。
总之,如果非双1时,可能会有问题,有兴趣可瞟一眼Yoshinori大佬report的
https://bugs.mysql.com/bug.php?id=70659
当然,上述所有的场景innodb_flush_log_at_trx_commit也记得设置为1。
简言之:
MASTER_AUTO_POSITION=1场景要依赖GTID信息,GTID信息依赖binlog。
安全配置建议
MySQL 5.5:
怎么配置都没有囊括100%的crash场景,极端情况下仍然会crash unsafe。
如果愿意牺牲性能,保证较高的安全性可以这样设置。
sync_relay_log_info = 1
relay_log_recovery = ON
MySQL 5.6:
单线程复制限定。
存储引擎限定:仅支持InnoDB,(TokuDB、RocksDB也应该可)。
主库:双1
从库(开不开MASTER_AUTO_POSITION通用):
relay_log_info_repository = TABLE
relay_log_recovery = ON
-- 即便是从库同样也请双1:
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
如果5.6开启了MTS,先不讨论,官方MySQL 5.6 MTS那时还算个玩具。
下篇见,直接讨论MySQL 5.7 MTS,那样的场景下将复杂一点点。
以上是关于MySQL#复制 - crash-safe Replication - 上的主要内容,如果未能解决你的问题,请参考以下文章