前言
备份恢复是 DBA 必备的技能,开源数据库 MySQL 在社区中有不少常用的备份恢复方案,xtrabackup,mypump,mydumper,mysqldump,mysql enterprise backup 等等。但是这些方法多数都是从外部利用各类数据库的机制来完成备份与回复,因此多多少少会存在操作步骤多,备份恢复比较慢等问题。于是 Oracle 在 19 年 7 月下旬发布的 MySQL 的 8.0.17 版本中,加入了一个全新的功能性插件:Clone。这个插件只需要几行 client 命令就可以完成数据库的备份恢复,且花费的时间远也低于常规的备份恢复手段。
原理介绍
Clone 插件如字面意思一般,它的作用就是 “Clone” 一个 MySQL 实例。安装这个插件之后,可以通过 MySQL client 登录到一个空的 MySQL 实例(推荐空实例),通过 Clone 命令获取到一份远端 MySQL 实例的一致性数据镜像,且整个过程和进度可以在 information_schema 的系统表中监测到。
简单的原理图如下:
Recipient 节点就是发起 Clone 操作的空实例;Donor 节点就是目标实例,是 Clone 操作的对象。当 Clone 操作完成之后,Recipient 节点就拥有 Donor 节点完整的 InnoDB 数据。从 8.0 开始,所有的 MySQL 系统表已经全部转换为 InnoDB 引擎,所以 Clone 操作也会把用户和授权信息完整的 Clone 到 Recipient 节点。
Clone 操作的标准流程如下:
- 清空 Recipient 节点的数据
- 复制 Donor 节点的数据文件
- 复制 Donor 节点的数据页
- 复制 Donor 节点的 redo log
- 同步 Donor 节点的数据文件
- 重启 Recipient 节点
- 在 Recipient 节点上完成 Crash recovery
使用 Clone 插件需要注意以下几点:
- Recipient 节点并不会 Clone Donor 节点的 MySQL 配置文件,因为 Recipient 节点一般会有不同的 IP 或者端口。但是涉及到一些存储相关的参数(例如 innodb_page_size)出现不一致的时候,Clone 插件会报错,这些参数可能会导致 Recipient 节点重启失败,因此要提前准备好 Recipient 节点的配置文件。
- Clone 插件复制的文件不包括 binlog,因为加入原有的 HA 结构并不需要 Donor 节点的 binlog。
- Clone 插件当前仅支持 InnoDB 引擎的表,MyISAM 和 CSV 等引擎的表在 Clone 之后是一个空表。Clone 插件最初设计的时候会支持 MySQL 的所有引擎,但是目前仅实现了 InnoDB 引擎的功能。
- Clone 操作会阻塞 Donor 节点上的所有 DDL 操作。
- Clone 操作会清空 Recipient 节点的所有数据和 binlog,因此要特别注意是否要在 Clone 操作执行之前备份 Recipient 节点的数据。
- Clone 操作最好是挂到后台执行(尽量避免网络问题)。
使用限制
- MySQL 版本不低于 8.0.17
- 到 8.0.17 版本为止,Clone 插件仅支持 InnoDB 引擎的表。
- Donor 节点在 Clone 的过程中,IO 读取的吞吐量和 CPU 使用率会有非常明显的上涨。
使用场景
由于 Clone 插件创建出来的数据库镜像的数据是符合一致性的,因此在搭建新的从库,创建临时测试库,本地、远端的一致性备份等可以直接访问源实例的场景会非常适合 Clone 插件。同时 Clone 插件也能满足 TB 级 MySQL 实例的备份需求,而 xtrabackup 和 mysqlbackup 会受到 redo log rotate 的影响,绝大多数场景下无法对 TB 级的 MySQL 进行备份,dump,快照等方式则存在磁盘空间或者时间上的缺陷。
操作流程
安装 Clone 插件
在 MySQL Client 安装 Clone 插件(建议都装上这个插件):
INSTALL PLUGIN CLONE SONAME "mysql_clone.so";
CREATE USER clone_user IDENTIFIED BY "clone_password";
GRANT BACKUP_ADMIN ON *.* to clone_user;
授予查看 Clone 进度等相关信息的权限:
GRANT SELECT ON performance_schema.* TO clone_user;
GRANT EXECUTE ON *.* to clone_user;
登录到 Recipient 节点执行 Clone 命令
##登录到Recipient的mysql client端执行
SET GLOBAL clone_valid_donor_list = 'donor.host.com:3306';
##Clone 操作推荐在shell命令行下挂到后台执行
CLONE INSTANCE FROM clone_user@donor.host.com:3306 IDENTIFIED BY "clone_password";
查看 Clone 的信息
SELECT STATE, CAST(BEGIN_TIME AS DATETIME) as "START TIME",
CASE WHEN END_TIME IS NULL THEN
LPAD(sys.format_time(POWER(10,12) * (UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(BEGIN_TIME))), 10, ' ')
ELSE
LPAD(sys.format_time(POWER(10,12) * (UNIX_TIMESTAMP(END_TIME) - UNIX_TIMESTAMP(BEGIN_TIME))), 10, ' ')
END as DURATION
FROM performance_schema.clone_status;
结果展示:
+-------------+---------------------+------------+
| STATE | START TIME | DURATION |
+-------------+---------------------+------------+
| In Progress | 2019-07-17 17:23:26 | 4.84 m |
+-------------+---------------------+------------+
检查 Clone 的操作进度:
SELECT STAGE, STATE, CAST(BEGIN_TIME AS TIME) as "START TIME",
CASE WHEN END_TIME IS NULL THEN
LPAD(sys.format_time(POWER(10,12) * (UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(BEGIN_TIME))), 10, ' ')
ELSE
LPAD(sys.format_time(POWER(10,12) * (UNIX_TIMESTAMP(END_TIME) - UNIX_TIMESTAMP(BEGIN_TIME))), 10, ' ')
END as DURATION,
LPAD(CONCAT(FORMAT(ROUND(ESTIMATE/1024/1024,0), 0), " MB"), 16, ' ') as "Estimate",
CASE WHEN BEGIN_TIME IS NULL THEN LPAD('0%', 7, ' ')
WHEN ESTIMATE > 0 THEN
LPAD(CONCAT(CAST(ROUND(DATA*100/ESTIMATE, 0) AS BINARY), "%"), 7, ' ')
WHEN END_TIME IS NULL THEN LPAD('0%', 7, ' ')
ELSE LPAD('100%', 7, ' ') END as "Done(%)"
FROM performance_schema.clone_progress;
结果展示:
+-----------+-------------+------------+------------+-----------+---------+
| STAGE | STATE | START TIME | DURATION | Estimate | Done(%) |
+-----------+-------------+------------+------------+-----------+---------+
| DROP DATA | Completed | 17:23:26 | 790.86 ms | 0 MB | 100% |
| FILE COPY | In Progress | 17:23:27 | 4.85 m | 94,729 MB | 47% |
| PAGE COPY | Not Started | NULL | NULL | 0 MB | 0% |
| REDO COPY | Not Started | NULL | NULL | 0 MB | 0% |
| FILE SYNC | Not Started | NULL | NULL | 0 MB | 0% |
| RESTART | Not Started | NULL | NULL | 0 MB | 0% |
| RECOVERY | Not Started | NULL | NULL | 0 MB | 0% |
+-----------+-------------+------------+------------+-----------+---------+
等待 Recipient 节点完成 Clone
重启完成 Crash Recovery 之后,整个 Clone 操作就结束了。
Clone 插件的效率测试
测试环境
为了防止物理资源的限制,测试使用了两台独立的物理机,基本配置为 Intel-E5 40 核的 CPU,128GB 的内存,3TB 的 Intel SSD。数据库版本使用 MySQL 8.0.17,用 sysbench 生成了 4 张测试表,每张表约 10 亿行数据,总的数据集大小约 1TB。
测试内容
测试主要对比的指标为时间,具体指从一个运行的实例上建立一个完整可用的镜像实例所花费的时间。测试的对比对象为 LVM 快照和 xtrabackup 工具。
MySQL 实例的配置使用相同的参数配置,数据目录均使用 SSD 磁盘,备份目录均使用 SAS 磁盘,redo log 为 2G 大小,总共 4 个文件。本次对比中会记录操作的各个阶段的耗时,数据仅作为实际备份恢复耗时的参考(部分数据可作为 TB 级 MySQL 实例的 RTO 的参考)。
xtrabackup 备份开启了压缩与流数据打包,备份的数据流存储到 SAS 盘,恢复过程中,整个解包和解压缩均在 SAS 盘完成。因此 Xtrabackup 的恢复备份流程总共包括以下几个步骤:打包压缩 -> 解包 -> 解压缩 -> 恢复备份 -> 本地 copy 数据文件 -> 成功启动,本次测试参考的流程为本地备份恢复。
快照完成的时间定为把完整的快照数据拷贝到 SAS 盘之后,本次测试不做实际快照备份的操作,仅以磁盘写入吞吐量的平均值来做粗略估计。快照备份的速度按照 SAS 盘的平均写入速度(200MB/s)来计算。快照恢复的速度按照 SAS 盘的平均读取速度(300MB/s)来计算。总共 1000GB 的数据量。
MySQL Clone 插件直接使用 Recipient 节点的数据目录(SSD 磁盘)为目标目录,Clone 插件的配置如下:
##设置为ON时,MySQL会忽略用户的设置,自动调整Clone的参数, 速度非常慢
##但是对服务器的CPU和IO资源几乎没有影响。
clone_autotune_concurrency = OFF
##设置传输数据时可用的buffer大小,默认大小为4MB,增加这个参数允许Clone插件更充分的利用IO设备的性能,加快Clone操作的速度。
clone_buffer_size = 268435456
##设置Clone操作尝试获取相关锁的等待时间
clone_ddl_timeout = 300
##设置Clone操作是否开启压缩功能来进行数据传输,会减少网络带宽的消耗,增加CPU的消耗
clone_enable_compression = ON
##设置Clone操作使用的并发线程数
clone_max_concurrency = 24
##设置Clone操作可使用的最大数据传输吞吐量
clone_max_data_bandwidth = 0
##设置Clone操作可使用的最大网络带宽
clone_max_network_bandwidth = 0
测试中模拟的实际业务场景分为四个:
- 无写入负载
- 低写入负载
- 中等写入负载
- 标准读写负载
xtrabackup 会因为 redo log 循环写入的原因导致备份失败,因此本次测试中不对高写入负载的场景进行测试,需要 Xtrabackup 支持 8.0.17 的 redo log archives 特性之后才能解决此类问题。
测试场景的几种负载由 sysbench 的脚本来进行模拟,对应的并发压力为:
- 无写入负载(None)
- 并发线程 = 0
- 低写入负载(LOW,write_only)
- 并发线程 = 4
- 中等写入负载(MID,write_only)
- 并发线程 = 8
- 标准读写负载(RW,read_write)
- 并发线程 = 16
- CPU idle% 约 60%
结论
- 如果从一个正常的 MySQL 实例上获取一个镜像实例或者例行备份,Clone 插件的效率远远高于(超过 100% 的速度提升)常规的备份恢复手段(mydumper,mysqlbackup,xtrabackup,快照等)。
- Clone 插件会占用 donor 实例的资源,会比较明显的影响 SELECT 操作,对 DML 的影响较小。
- 如果 MySQL 实例处于异常状态,无法访问,那么 Clone 插件就无法使用了,依旧需要使用常规的手段来进行灾难恢复。
耗时对比
实测过程中,Clone 插件对 donor 实例的主要压力在于 CPU,以及 IO 设备的读取性能上,对写入性能的影响相对比较小,即使达到中等量级的写入压力,Clone 的过程中也只达到了约 8% 的写入性能损失,但是在标准的读写负载场景下,性能损失达到了约 28%,主要是因为 CPU 的资源发生了争抢。
虽然在规划中没有加入高写入负载的测试,但是实际发现即使 sysbench 的写入并发设置为 1(写入 QPS 约 4000),xtrabackup 依旧不能完成备份操作(由于 redo log 的循环写入),因此实际测试的结果中,xtrabackup 仅有无负载的测试数据。
横向对比几种不同的备份恢复方案(所有图例的 None 字段代表负载):
从图中可以看到 Clone 插件花费的时间仅有快照的 40%,Xtrabackup 的 18%,这是因为 Clone 的过程几乎就是从远端实例把数据 rsync 到本地的 SSD 磁盘,整个过程没有 SAS 盘的参与。
纵向对比 Clone 插件在不同场景下的表现:
可以看到随着写入负载,或者是读写负载的增加,Clone 插件的耗时也会增长,增加的幅度最多达到了 50%,因此 Clone 操作最好在业务比较少的时候进行,或者是在其他空闲 MySQL 实例进行。
结语
MySQL 8 还在随着版本不断地添加新功能,慢慢的,也许高 QPS,高并发,微秒级查询等 MySQL 的短板在未来会慢慢的都补上,是时候拥抱新技术带来的效率提升与改变了。