摘要:一旦发生宕机故障或应用停机,将给企业带来巨大的经济损失。

信息化建设的不断推进,让各企业机关的活动越来越多依赖于关键业务信息系统,并在整个机构的运营和发展中起着至关重要的作用。一旦发生宕机故障或应用停机,将给机构带来巨大的经济损失。

对那些需要保障信息安全和提供不间断的信息服务的机构来说,业务系统的容错性和不间断性显得尤为重要。如何保障各种关键应用持续运营,达到永续经营的良性循环,已成为当今企事业单位和IT领域急需解决的关键问题。
本文详细记录了华为云技术专家在 MySQL 实践中遇到的数据高可用备份问题,看似简单的“主备切换”和“备机备份”背后,有哪些隐患?

生产环境数据库的主备高可用架构

生产环境的数据库多数是带备份的,至少是一主一备,目的是当主机出现宕机但是又不能很快恢复时,把业务切换到备机上,快速恢复业务,这是常说的高可用。

但是备机也可能会故障,有时候还需要基于当前的数据做一份新的数据库出来,此时怎么做? 是不是还得一个冷备份呢?所以基本的数据库架构很多是这样的:

mysql主备状态 查看Slave_IO_Running和Slave_SQL_Running mysql主备机宕机自动切换_数据库


数据库基本架构最初我们做RDS for MySQL 备份也是基于这样的模型,但不同的是,我们引入了对象存储系统(OBject Storage)。因为实例较多,发生切换时不能靠人工处理,因此引入了高可用系统(High Available)。针对备份,我们也抽象出一个独立系统,用来处理复杂的备份和还原过程,称之为备份恢复系统(Backup and Restore)。最终的框架图如下:

mysql主备状态 查看Slave_IO_Running和Slave_SQL_Running mysql主备机宕机自动切换_数据库_02


高可用备份框架图

优化:
触发备份时,可能会导致主机资源被占用,例如压缩时占用 CPU 资源,上传会占用IO,所以业界RDS for MySQL都会在备机上做备份,但是仅仅是换个主机这么简单吗? 下面重点说说我们在此过程中踩过的一个坑吧!

敲黑板化重点:
基于备机备份第1个带来的问题我们要正视,那就是可能的延迟,整体上延迟时间为:
OBS上的数据时间 晚于 备机的数据时间 晚于 主机的数据时间
即:OBS上的数据可能晚于备机的数据,而后者又晚于主机的数据。
这个延迟会带来其它问题,我们在做高可用和可靠性混合测试时碰到过。

假设A的uuid为A,B的uuid为B,具体操作步骤和数据如下:

mysql主备状态 查看Slave_IO_Running和Slave_SQL_Running mysql主备机宕机自动切换_数据库_03


mysql主备状态 查看Slave_IO_Running和Slave_SQL_Running mysql主备机宕机自动切换_数据_04


看下我们测试的结果:

mysql主备状态 查看Slave_IO_Running和Slave_SQL_Running mysql主备机宕机自动切换_技术_05


图1 新备机

mysql主备状态 查看Slave_IO_Running和Slave_SQL_Running mysql主备机宕机自动切换_数据_06


图2 新主机

大家注意,如果这部分数据没有同步过来其实是一个非常危险的动作。因为此时主备复制关系都是正常的,所以很容易被忽略。而且由于这样的延迟关系,该场景下的问题的发生是一个大概率事件。

很显现,MySQL并没有按照大家想象中那样去做。很多 DBA 都做过这样的实验,把已经从主机同步过来的数据在备机删除掉,然后设置已经执行的 GTID 为之前的值,数据会重新从主机同步过来,而且只认 GTID 不认内容,可是为什么现在就不行了呢?

在此过程中,我们进行了各种检查。先是验证是否备份出现问题,然后又验证是否还原步骤出现问题,然而都不是…… 总不能是数据库 A 通过识别 GTID 发现,有部分数据是以自己的 uuid 开头,所以才没有同步吧?

所以我们尝试修改了A主机的server-uuid为C,但并没有任何结果。这样的结果,一度让我的思路陷入僵局,感觉大脑中的主备复制知识要被刷新了。打开解析后的binlog,看到 binlog 中使用的是 server ID,我想mysql是不是通过server ID来识别呢?抱着病重乱投医的心态,我尝试修改了 server ID,结果却意外地成功了!于是我们又顺着这条线索,查看 MySQL 官方文档,发现一个不常用参数。

–replicate-same-server-id

mysql主备状态 查看Slave_IO_Running和Slave_SQL_Running mysql主备机宕机自动切换_技术_07


To be used on slave servers. Usually you should use the default setting of 0, to prevent infinite loops caused by circular replication. If set to 1, the slave does not skip events having its own server ID. Normally, this is useful only in rare configurations. The option cannot be set to 1 when --log-slave-updates is enabled, which is the default.

By default, the slave I/O thread does not write binary log events to the relay log if they have the slave’s server ID (this optimization helps save disk usage). If you want to use --replicate-same-server-id, be sure to start the slave with this option before you make the slave read its own events that you want the slave SQL thread to execute.

–log-slave-updates enables replication servers to be chained. For example, you might want to set up replication servers using this arrangement:

A -> B -> C

Here, A serves as the master for the slave B, and B serves as the master for the slave C. For this to work, B must be both a master and a slave. With binary logging and the --log-slave-updates option enabled, which are the default settings, updates received from A are logged by B to its binary log, and can therefore be passed on to C.

大意是说:
为防止循环复制引起的无限循环,备机默认设置通常为0。一旦设置为1,备机会忽略那些同自己具有相同server ID的事件,所以会出现上面我们遇到的问题。

测试了一遍,是它!
又测试一遍,是它!
再测试一遍,还真是它!
本以为找到了救命的灵丹妙药,但接下来的问题还是让我们面面相觑。

官方文档写明replicate-same-server-id 与 log-slave-updates 互斥关系,而log-slave-updates则是我们在备机做备份必须开启的参数,所以该参数我们不能设置为on,这就相当尴尬了……

目前这个问题只能规避,例如在每次恢复的时候,重新设置server ID 为一个不重复的值。而且 MySQL不推荐使用重复的 ID 值,也是为了防止出现无限循环复制。

文章到此结束,该问题的发现、重现、定位及解决,感谢所有兄弟们的支持,在追求数据的最大可靠性上,我们从不懈怠。

此外,补充一个注意事项,就是在备机做运维动作时,对于修改数据、flush等这类会写bin-log 的操作时,一定要先执行 set sql_log_bin=0,否则你的每一笔操作,都会造成备机少同步一条数据。