背景
最近线上使用 Xtrabackup 做热备份的时候遇到一个非常奇怪的问题: 线上在从库上进行热备的时候,从库的 SQL 线程堵在了 waiting for global read lock
上,时间最长的堵了 7 小时。
原理
在摆开问题之前先来看下 Xtrabackup 2.2.12 备份的原理。
xtrabackup 2.3 之前有两个程序:一个是 innobackupex perl 脚本,一个是 xtrabackup c 程序。xtrabackup 负责拷贝跟 innodb 相关的文件,innobackupex 主要负责拷贝非 innodb 文件,两个程序通过创建和删除中间文件来同步,以便能做到一个最终一致的备份。两个程序使用创建和删除文件方式进行同步的过程如下:
- xtrabackup 创建 xtrabackup_suspended_1, innobackupex 删除该文件。该文件和增量备份逻辑有关,这里不分析。
- xtrabackup 创建 xtrabackup_suspended_2, 表示跟 innodb 相关的文件(增量 redo,idb 文件,ibdata1)都已拷贝完成并阻塞,这时候 innobackupex 如果检测到该文件存在则往里面写入 xtrabackup 的 pid,接着进行下一步的 FTWRL 操作,进而开始拷贝 non-innodb 文件。
- innobackupex 拷贝完 non-innodb 文件,并记录 binlog 信息后就简单的将文件 xtrabackup_suspended_2 删除(unlink),innobackupex 接着进入阻塞状态。xtrabackup 检测到该文件被删除后就继续开始拷贝增量日志信息(redolog)。
- xtrabackup 拷贝完所有增量日志后(LSN 不再变化,因为加了 FTWRL 锁以后正常 LSN 就不会再增加了)会创建 xtrabackup_log_copied 文件,innobackupex 检测到文件创建后先将 xtrabackup_log_copied 删除,然后释放全局读锁,接着有需要的话 start slave sql_thread,最后等待 xtrabackup 进程结束。到这里整个备份基本结束了。
- xtrabackup 检测到 xtrabackup_log_copied 被删除后自己释放资源并结束,innobackupex 发现子进程结束后再接着做其它后续工作。
一些细节
innobackupex 脚本在调用 xtrabackup 进行流式备份(tar)的时候会将 xtrabackup_logfile 和 xtrabackup_suspended 文件创建在 tmpdir 目录中(非流式备份会将 suspended 文件拷贝至备份目录,因为一般备份目录不同端口都不同,所以不会有问题),xtrabackup_logfile 文件会等日志(redo)拷贝完成后再传到远程,而 xtrabackup_suspended 文件则是中间生成的同步文件。
问题回顾
观察线上的备份日志,可以看到晚上 8:09 分开始加全局读锁准备拷贝非 InnoDB 文件,按照之前的原理分析,正常情况下后面不会再有拷贝 idb 文件的情况,但是却发生了。
可以看到用了 20 秒的时间把所有的非 InnoDB 表备份走了,但没有释放全局读锁。其实 20 秒的时间影响也比较大了,在主库上备份更需要慎重考虑。
两个小时后备份结束了,并释放了全局读锁。
这种情况有悖于该工具所描述的 “不影响线上服务” 的宗旨。于是决定去看下到底是什么原因。
线上的一些环境
在描述之前补充下当时备份的一些环境
- Xtrabackup 版本是 2.2.12
- MySQL 版本是 5.5,tmpdir 用的配置文件中指定的,路径一样
- 备份方式是 xtrabackup + tar 压缩的全量备份
- 出现备份问题的都是用的上面的备份方式
- 出现备份问题的机器上使用流式备份的端口至少 2 个
思路
先整理了下线上的一些环境和问题的共性,接着开始排查。排除了新旧备份的使用上的区别,无果。不甘心去看了代码,整理了下拷贝 idb 文件、非 innodb 文件和加全局读锁的顺序。结合之前的原理分析,再考虑线上环境,怀疑在拷贝完 innodb 文件之前,应该是有别的进程创建了 suspended 文件,导致出现问题,正好同一机器上的备份端口不止一个,并且创建的 suspended 文件都在同一目录下,而且中间文件的文件名都一样。如果两个端口同时开始备份,数据量小的端口先创建了 suspended 文件,那么另一个数据量大的端口就会出现开头所描述的问题。经测试后重现。
解决办法
- 加上 tmpdir 参数,指定不同的目录(这个代价比较小)
- xtrabackup 创建的文件名需要改下,innobackupex 脚本也得改下,使得每个备份进程创建的中间文件名都不一样,比如加上 xtrabackup 进程的 id
- 直接上 xtrabackup 2.3 或者最新版(2.2 是当时上的时候的最新版本),因为 2.3 以后的版本程序 Percona 全部用 c 重写了
总结
该问题出现需要满足的三个条件
- 同一机器上有至少 2 个端口在备份
- 使用流式备份
- suspended 中间文件的创建路径相同