接【MySQL#复制 - crash-safe Replication - 上】,继续看5.7的。同样只考虑全事务引擎的情况,非事务引擎忽略。本篇将分析5.7版本下,支持单线程和MTS复制的crash-safe配置,同时将讨论传统file & pos与使用GTID + MASTER_AUTO_POSITION的场景。
在单线程复制的情况下,5.7和5.6开关GTID的crash-safe其实可以简单理解为“没有差别”:
如果就这样单线程复制场景讨论完了,也太敷衍了,所以,这里还要提一下MySQL 5.7 GTID引入的一个新特性:
在MySQL 5.6,从库开启GTID必须配置log_bin
与log_slave_updates
是因为如果从库重启后,它自己没办法知道自己执行了哪些GTID。@@gtid_executed
是一个内存值,没有持久化。当时的做法是通过持久化了的binlog获取对应GTID信息。
5.7这个新特性就舒服了,将GTID信息存入了mysql.gtid_executed
表中,所以无需开启log_bin
或log_slave_updates
也可以开启GTID。不开binlog的好处也很明显,在永远不会提升为主库的从库上,或者不用级联的从库上,可以免去不必要的存储和性能开销。
因为上面的特性,可以思考一下:目前版本,开启log_bin
,且关闭log_slave_updates
的情况下,mysql.gtid_executed
是实时更新的,那么这时,是不是也可以保证crash前后的GTID准确呢?关于这个问题,放到后面一起说。
好家伙,MTS环境下,就复杂亿点点。因为有多个worker
线程和一个coordinator
去做原本SQL Thread
做的事情。
多个worker
将自身apply events
的进度记录在mysql.slave_worker_info
中,但还好该表也为事务表。换言之:MTS环境中每次apply events的时候,都会更新mysql.slave_worker_info
,而mysql.slave_relay_info
在每次做MTS检查点时做更新,所以slave relay info
变得不是实时的了(和单线程不同)。
关于MTS做checkpoint的内容,可以参考两个参数:
slave_checkpoint_group
slave_checkpoint_period
所以!————这里就会出现比单线程复制更麻烦问题了:
1)、MTS可能出现gap事务。即后提交的事务可能先做了apply,之前的事务可能还没有结束,如之前是一个大事务,这个好理解。
2)、Exec_Master_Log_Pos
不准确。因为MTS不会时时刻刻更新relay log info
。称它为Gap-free low-watermark
。官方手册里现在叫做Source binary log position lag
上述这两个问题,可阅读手册: https://dev.mysql.com/doc/refman/5.7/en/replication-features-transaction-inconsistencies.html
(懒得复制可以扫码跳转)
这两个问题的成因如果不理解,可以阅读一下MTS的相关内容。翻手册、google、看八怪老师的《深入理解MySQL主从原理》的第19和第20讲也可。这块的内容,有空我也再复习整理一下。
有了上面的背景知识,接下来想一下怎么配置能保证crash safe。
尽量安全的配置(供参考):
master_info_repository = TABLE
sync_master_info = 1
relay_log_info_repository = TABLE
relay_log_recovery = ON
sync_relay_log = 1
slave_preserve_commit_order = ON
这里涉及到一个知识点就是,MTS在file+pos
的情况下是怎么做恢复的,这个过程我是参考八怪老师的《深入理解MySQL主从原理》的第25讲,细节也可以参考一下init_slave
函数。
分析一下:
1)这个场景下,mysql.slave_master_info
的信息不会被覆盖,所以master info
需要保证可靠,所以需要放在表里,并且将sync_master_info
设置为1。
关于sync_master_info
参数:
手册里描述如下:
master_info_repository = TABLE. If the value of sync_master_info is greater than 0, the replica updates its connection metadata repository table after every sync_master_info events. If it is 0, the table is never updated.
2)这里的第二个核心关键点是sync_relay_log
参数要设置为1,因为MTS且未开MASTER_AUTO_POSITION
的场景下,在crash后是需要依赖relay log
去恢复Gap事务的,所以需要保证relay log
的完整性。这样的配置首先性能受到很大影响,其次,如MySQL 5.5版本一样会存在安全问题,因为极端情况下写文件行文本身不靠谱,仍可能至少丢一个event。
读取relay log恢复调用栈:
Relay_log_info::rli_init_info()
->init_recovery() // relay_log_recovery打开
->mts_recovery_groups()
总之,只要relay log
丢失,就麻烦了,相关"bug":
https://bugs.mysql.com/bug.php?id=81840
(懒得复制可以扫码跳转)
关于这个问题,其实有个叫Jean-François Gagné
的老哥提出了一个“临时性”的解决方案(眼熟一下,后面还有这个大佬):
1) sync_relay_log = 1
2) slave_preserve_commit_order = ON
(when slave_parallel_mode = LOGICAL_CLOCK)
这样设置,主要是为了消除Gap。slave_preserve_commit_order
参数的功能如它自己的名字,细节可以翻一下手册。
至此,这个场景在上面的配置后,已经可以竭尽所能保证crash safe
了,有点勉强,性能还不好。
JFG这个老哥,同时也给官方提供了一个【改进建议】(现在MySQL版本没实现): To solve Bug#81840, we need to 1st download binlog and then to fix the relay log position in the mysql.slave_worker_info table. This is tedious, but not overly complicated.
这个建议的理论基础是:MTS更新mysql.slave_worker_info
的行为,是实时可靠的。有了这个基础,“理论上”,就可以知道从哪些地方追补binlog了。
至少目前版本好像真没办法在这样的场景保证crash safe
。换言之,如果基于relay log
的恢复能够做好,也就是bug#81840
及相关问题,官方能够搞定,以后在用MTS+非GTID
场景下,也能保证了。
file+pos
做crash后恢复,难点在于,恢复过程依赖relay log,而relay log的完整性难以保证,那么——启用MASTER_AUTO_POSITION
呢?那之前的问题好像都不是问题了。故这种场景就非常简单了:
各种原因crash之后,比较主和从库的GTID信息似乎就好了。这个过程大致如下:
因为依赖GTID,所以只需要保证GTID信息没问题就行了。那,依赖relay log
补Gap也是不是没有意义了?——的确,DBA们是这样想的,但一开始MySQL没这样做,后来官方的复制团队改了(不了解契机,但好像是被怼了之后改的):
比如在宕机前从库的Executed_Gtid_Set
为$server_uuid:1-100:102-105:108-120,那么实际上只要拿到缺失GTID对应的event即可,主库binlog dump GTID
线程只需要发送101、106-107就恢复完成了。
所以……这个过程可以不依赖relay log
。同时,也不需要mysql.slave_worker_info
的内容了。
安全可靠的配置就更简单了,如下:
-- 双1
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
relay_log_recovery = ON (5.7.28前需要配置这个)
对,特别简单,双1就完事,这样,终于不用依赖relay log
了,所以也不需要保证relay log
的完整性了。
在5.7.28+/8.0.18+
后:
relay_log_recovery
这个参数也变得无所谓了,真正的实现了通过GTID即可完整恢复。
不同版本MTS的recovery过程有差,所以大概整理了一下,参考如下:
relay log
(请保证它完整),且有点麻烦,也是产生ERROR 1872的一个原因之一,这个之前写过。relay log
(依然需要它完整)。依旧僵硬。因为上面分析到,在GTID的MASTER_AUTO_POSITION
场景下,理论上是不需要依赖relay log
的。于是JFG老哥又去找官方battle了,细节可以看这个链接: https://bugs.mysql.com/bug.php?id=92882
(懒得复制可以扫码跳转)
他的意思如下: Do not fail relay-log-recovery in the case of vanished relay logs when GTID Auto_Position is enabled as the table mysql.slave_worker_info is not needed in this case.
MASTER_AUTO_POSITION
打开的情况下,跳过relay log
恢复,通过GTID信息恢复。终于不需要保证relay log
的完整性了。
对应的Changelog如下: On a multi-threaded slave with GTIDs in use and MASTER_AUTO_POSITION set to ON, following an unexpected halt the slave would attempt relay log recovery, which failed if relay logs had been lost, preventing replication from starting. However, this step was unnecessary as GTID auto-positioning can be used to restore any missing transactions. In a recovery situation, the slave now checks first whether MASTER_AUTO_POSITION is set to ON, and if it is, skips relay log recovery.
比较好理解,就不翻译了。
总结一下如下:
MySQL 5.7.28+
版本。非GTID下的传统复制并不能保证100%的可靠,而且性能不佳。建议打开GTID + MASTER_AUTO_POSITION
,并保证双1
。显然,GTID机制比file & pos
的方式要更智能。
有一说一,GTID都诞生很多年了(好像有7、8年了),实在是比传统位点复制先进多了,为什么不用呢?
上面分析了开启MASTER_AUTO_POSITION
时,无论单线程还是MTS,都需要依赖GTID,而GTID信息正确这个需求,得保证binlog完整。——那么从库关闭binlog时会可行吗?或者sync_binlog != 1
时,能否保证crash safe?因为毕竟这样的场景是可以提升从库性能的。
从理论上讲,我感觉行,因为5.7后,GTID信息也存在mysql.gtid_executed
表里持久化了。好像也可以不依赖binlog。
随手搜一下,果然还是Jean-François Gagné
出手了,他提了一个类似的场景给官方。大概意思是想又要性能又可以满足安全:
https://bugs.mysql.com/bug.php?id=92109
(懒得复制可以扫码跳转)
目前为止,官方给的回复是将该场景作为特性类,不属于bug类:
I'm switching this to "Feature request"
有趣的是,底下还有赞同JFG老哥,并催进度的: Just reading all the articles of Jean-François and I think the solution is fairly easy and an important fix. Any idea about the current status of implementation?
MySQL社区真好玩儿。
在MTS环境中,且有Gap的时候,如果不启用GTID,各大备份工具(mysqldump\mydumper\xtrabackup... )是怎么处理这个问题的。
google一下《Demystifying MySQL Replication Crash Safety》,这个就是上面一直在和官方斗智斗勇的JFG老哥在Percona Live 2018上分享的一个话题。如果有兴趣可看看。
其他几篇文章: