XtraBackup备份与恢复实战

XtraBackup备份与恢复实战

XtraBackup备份与恢复实战

1. 前言

Xtrabackup是Percona公司开源的一款基于InnoDB引擎的在线热备工具,其下载地址为https://www.percona.com/downloads/Percona-XtraBackup-LATEST。xtarbackup包含两个主要的工具,即xtrabackup和innobackupex,二者区别如下:

  • xtrabackup智能备份innodb和xtradb两种引擎,而不支持myisam引擎
  • innobackupex 是一个封装了xtrabackup的Perl的脚本,支持同时备份innodb和myisam(需要加全局读锁和不支持增备)

2. 安装

官方有提供rpm包,工具类型的,一般都无需手动编译安装,直接下载官方的rpm,安装即可

image-20200505163007229

3. 常用选项

Usage: [innobackupex [--defaults-file=#] --backup | innobackupex [--defaults-file=#] --prepare] [OPTIONS]
--defaults-file=[MY.CNF]    //指定配置文件:只能从给定的文件中读取默认选项。 且必须作为命令行上的第一个选项;必须是一个真实的文件,它不能是一个符号链接。

--databases=#    //指定备份的数据库和表,格式为:--databases="db1[.tb1] db2[.tb2]" 多个库之间以空格隔开,如果此选项不被指定,将会备份所有的数据库。

--include=REGEXP    //用正则表达式的方式指定要备份的数据库和表,格式为 --include=‘^mydb[.]mytb’ ,对每个库中的每个表逐一匹配,因此会创建所有的库,不过是空的目录。--include 传递给 xtrabackup --tables。

--tables-file=FILE    //此选项的参数需要是一个文件名,此文件中每行包含一个要备份的表的完整名称,格式为databasename.tablename。该选项传递给 xtrabackup --tables-file,与--tables选项不同,只有要备份的表的库才会被创建。

注意:部分备份(--include、--tables-file、--database)需要开启 innodb_file_per_table 。

--compact    //创建紧凑型备份,忽略所有辅助索引页,只备份data page;通过--apply-log中重建索引--rebuild-indexs。

--compress    //此选项指示xtrabackup压缩备份的InnoDB数据文件,会生成 *.qp 文件。

--decompress    //解压缩qp文件,为了解压缩,必须安装 qpress 工具。 Percona XtraBackup不会自动删除压缩文件,为了清理备份目录,用户应手动删除 * .qp文件:find /data/backup -name "*.qp" | xargs rm。

--no-timestamp    //指定了这个选项备份将会直接存储在 BACKUP-DIR 目录,不再创建时间戳文件夹。

--apply-log    //应用 BACKUP-DIR 中的 xtrabackup_logfile 事务日志文件。一般情况下,在备份完成后,数据尚且不能用于恢复操作,因为备份的数据中可能会包含尚未提交的事务或已经提交但尚未同步至数据文件中的事务。因此,此时数据文件仍处于不一致状态。“准备”的主要作用正是通过回滚未提交的事务及同步已经提交的事务至数据文件使得数据文件处于一致性状态。

--use-memory=#    //此选项接受一个字符参数(1M/1MB,1G/1GB,默认100M),仅与--apply-log一起使用,该选项指定prepare时用于崩溃恢复(crash-recovery)的内存。

--copy-back    //拷贝先前备份所有文件到它们的原始路径。但原路径下不能有任何文件或目录,除非指定 --force-non-empty-directories 选项。

--force-non-empty-directories    //恢复时指定此选项,可使 --copy-back 和 --move-back 复制文件到非空目录,即原data目录下可以有其他文件,但是不能有与恢复文件中同名的文件,否则恢复失败。

--rsync    //此选项可优化本地文件(非InnoDB)的传输。rsync工具一次性拷贝所有非InnoDB文件,而不是为每个文件单独创建cp,在备份恢复很多数据库和表时非常高效。此选项不能和 --stream 一起使用。

--incremental    //这个选项告诉 xtrabackup 创建一个增量备份,而不是完全备份。它传递到 xtrabackup 子进程。当指定这个选项,可以设置 --incremental-lsn 或 --incremental-basedir。如果这2个选项都没有被指定,--incremental-basedir 传递给 xtrabackup 默认值,默认值为:基础备份目录的第一个时间戳备份目录。

--incremental-basedir=DIRECTORY    //该选项接受一个字符串参数,该参数指定作为增量备份的基本数据集的完整备份目录。它与 --incremental 一起使用。

--incremental-dir=DIRECTORY    //该选项接受一个字符串参数,该参数指定了增量备份将与完整备份相结合的目录,以便进行新的完整备份。它与 --incremental 选项一起使用。

--redo-only    //在“准备基本完整备份” 和 “合并所有的增量备份(除了最后一个增备)”时使用此选项。它直接传递给xtrabackup的 xtrabackup --apply-log-only 选项,使xtrabackup跳过"undo"阶段,只做"redo"操作。如果后面还有增量备份应用到这个全备,这是必要的。有关详细信息,请参阅xtrabackup文档。

--parallel=NUMBER-OF-THREADS    //此选项接受一个整数参数,指定xtrabackup子进程应用于同时备份文件的线程数。请注意,此选项仅适用于文件级别,也就是说,如果您有多个.ibd文件,则它们将被并行复制; 如果您的表一起存储在一个表空间文件中,它将不起作用。

4. 执行流程

  1. innobackupex备份过程

    当备份开始时,innobackupex首先会开启一个后台检测进程,用于实时检测mysql redo日志的变化,一旦发现redo中有新的日志写入,立刻将日志记录到xtrabackup_log中。之后复制innodb的数据文件和系统表空间文件ibdata1,待复制完毕后,执行flush tables with read locak(防止数据表发送DDL操作,并且这一块获取binlog的位置),复制.frm,MYI,MYD等文件,最后执行unlock tables,把表设置为可读可写状态,最终停止xtrabackup_log

    image-20200505175646450

  2. 全备恢复

    在恢复数据的时候,需要先执行apply log ,在这一阶段,xtrabackup会启用内嵌的innodb实例,恢复xtrabackup_log中的内冲,将事务信息变更应用到innodb数据/表空间,同时回滚未提交的事务(类似innodb示例恢复),如下图:

    image-20200505175703463

  3. 增量备份

    增备主要时对innodb引擎来说的,主要通过拷贝innodb中有变更的“页”(通过对比数据页的LSN是否大于xtrabackup_checkpoints给定的LSN)。增量备份时基于全备的,第一次增量备份的数据必须要基于上一次的全备,后面增备基于上次增备,过程如下图:

    image-20200505175714693

  4. 增备恢复

    和全备恢复类似,需要两步,应用日志和恢复数据;不同的是需要把增量的数据应用到全备中,按次序,然后再拷贝/移动过去,如下图所示:

    image-20200505175730246

    image-20200505175738934

5. 实战操作

  1. 全量备份

    # 1.创建专门用于备份的用户
    15:03:02pm> create user 'backup'@'localhost' identified by 'pwd';      
    Query OK, 0 rows affected (0.06 sec)
    
    # 注意:必须要有process权限
    15:03:27pm> grant reload,lock tables,replication client,create tablespace,process,super on *.* to 'backup'@'localhost';
    Query OK, 0 rows affected, 1 warning (0.09 sec)
    
    15:04:03pm> 
    # 2.进行全备份,innobackupex 会自动创建一个目录,是时间戳
    # 测试数据如下
    15:15:42pm> select * from test.users;
    +----+-----------+-------+---------------------+
    | id | email     | name  | creation_time       |
    +----+-----------+-------+---------------------+
    |  1 | test@com  | test  | 2020-05-07 15:15:35 |
    |  2 | test1@com | test1 | 2020-05-07 15:15:42 |
    +----+-----------+-------+---------------------+
    2 rows in set (0.00 sec)
    
    # 3.开始全备
    [root@~]# innobackupex --defaults-file=/etc/my.cnf --user=backup --password=passwd --socket=/tmp/mysql.sock /data/backup/
    200507 15:53:14  version_check Connecting to MySQL server with DSN 'dbi:mysql:;mysql_read_default_group=xtrabackup;mysql_socket=/tmp/mysql.sock' as 'backup'  (using password: YES).
    Failed to connect to MySQL server as DBD::mysql module is not installed at - line 1327.
    200507 15:53:14 Connecting to MySQL server host: localhost, user: backup, password: set, port: not set, socket: /tmp/mysql.sock
    Using server version 5.7.17-11
    innobackupex version 2.4.6 based on MySQL server 5.7.13 Linux (x86_64) (revision id: 8ec05b7)
    xtrabackup: uses posix_fadvise().
    xtrabackup: cd to /data/database/mysql/
    xtrabackup: open files limit requested 65535, set to 65536
    xtrabackup: using the following InnoDB configuration:
    xtrabackup:   innodb_data_home_dir = .
    xtrabackup:   innodb_data_file_path = ibdata1:12M:autoextend
    xtrabackup:   innodb_log_group_home_dir = ./
    xtrabackup:   innodb_log_files_in_group = 2
    xtrabackup:   innodb_log_file_size = 134217728
    xtrabackup: using O_DIRECT
    InnoDB: Number of pools: 1
    200507 15:53:14 >> log scanned up to (1501132459)
    xtrabackup: Generating a list of tablespaces
    InnoDB: Allocated tablespace ID 9 for mysql/time_zone, old maximum was 0
    ...
    ...
    ...
    200507 15:53:21 Executing UNLOCK BINLOG
    200507 15:53:21 Executing UNLOCK TABLES
    200507 15:53:21 All tables unlocked
    200507 15:53:21 [00] Copying ib_buffer_pool to /data/backup/2020-05-07_15-53-13/ib_buffer_pool
    200507 15:53:21 [00]        ...done
    200507 15:53:21 Backup created in directory '/data/backup/2020-05-07_15-53-13/'
    200507 15:53:21 [00] Writing backup-my.cnf
    200507 15:53:21 [00]        ...done
    200507 15:53:21 [00] Writing xtrabackup_info
    200507 15:53:21 [00]        ...done
    xtrabackup: Transaction log of lsn (1501132450) to (1501132459) was copied.
    200507 15:53:21 completed OK!
    
    # 注意:以下错误不影响正常备份和恢复,无需纠结
    Failed to connect to MySQL server as DBD::mysql module is not installed at - line 1327.
    
    # 如果要使用--defaults-file ,必须为第一个参数才行,不然会报错如下:
    xtrabackup: Error: --defaults-file must be specified first on the command line
    
    # 查看下备份后的文件
    [root@ ~]# ll /data/backup/2020-05-07_15-53-13/
    total 141M
    -rw-r----- 1 root root  427 May  7 15:53 backup-my.cnf
    -rw-r----- 1 root root 1.2K May  7 15:53 ib_buffer_pool
    -rw-r----- 1 root root 140M May  7 15:53 ibdata1
    drwxr-x--- 2 root root 4.0K May  7 15:53 mysql
    drwxr-x--- 2 root root 4.0K May  7 15:53 performance_schema
    drwxr-x--- 2 root root  12K May  7 15:53 sys
    drwxr-x--- 2 root root 4.0K May  7 15:53 test
    -rw-r----- 1 root root  119 May  7 15:53 xtrabackup_checkpoints  # 增量备份会用到
    -rw-r----- 1 root root  483 May  7 15:53 xtrabackup_info # 如果有开启binlog或gtid会记录相关位点
    -rw-r----- 1 root root 2.5K May  7 15:53 xtrabackup_logfile
    
  2. 全备恢复

    # 恢复备份到mysql的数据文件目录,这一过程需要先关闭mysql,移走数据目录,应用日志,恢复数据,授权,启动
    [root@ ~]# /etc/init.d/mysql stop
    [root@ ~]# mv /data/database/mysql /data/backup/
    [root@ ~]# innobackupex --apply-log --use-memory=3G /data/backup/2020-05-07_15-53-13
    .....
    InnoDB: 32 non-redo rollback segment(s) are active.
    InnoDB: Waiting for purge to start
    InnoDB: 5.7.13 started; log sequence number 1501133333
    xtrabackup: starting shutdown with innodb_fast_shutdown = 1
    InnoDB: FTS optimize thread exiting.
    InnoDB: Starting shutdown...
    InnoDB: Shutdown completed; log sequence number 1501133352
    200507 16:07:23 completed OK!
    [root@ ~]# mv /data/backup/2020-05-07_15-53-13 /data/database/mysql
    [root@ ~]# chown -R mysql.mysql /data/database/mysql
    [root@ ~]# /etc/init.d/mysql start
    Starting MySQL (Percona Server)..... SUCCESS!
    
    # 要使用--copy-back 或 --move-back 不建议使用mv,本文只是用来测试一下
    # --use-memory 指定可用内存,可以提高应用日志的速度
    # 验证还原后的数据
    16:16:32pm> select * from test.users;
    +----+-----------+-------+---------------------+
    | id | email     | name  | creation_time       |
    +----+-----------+-------+---------------------+
    |  1 | test@com  | test  | 2020-05-07 15:15:35 |
    |  2 | test1@com | test1 | 2020-05-07 15:15:42 |
    +----+-----------+-------+---------------------+
    2 rows in set (0.04 sec)
    
  3. 增量备份

    # 基于上一次的全备,来一次增备,先让数据有点变化吧,如下
    16:22:48pm> insert into users(email,name) values('test2@com','test2');
    Query OK, 1 row affected (0.13 sec)
    
    16:22:50pm> delete from users where id=2;
    Query OK, 1 row affected (0.03 sec)
    
    16:23:08pm> select * from test.users;                                 
    +----+-----------+-------+---------------------+
    | id | email     | name  | creation_time       |
    +----+-----------+-------+---------------------+
    |  1 | test@com  | test  | 2020-05-07 15:15:35 |
    |  3 | test2@com | test2 | 2020-05-07 16:22:50 |
    +----+-----------+-------+---------------------+
    2 rows in set (0.00 sec)
    
    # 上面的数据内容变化为 插入test2,删了test1
    # 下面我们来次增备
    [root@ ~]# innobackupex --defaults-file=/etc/my.cnf --user=backup --password=xkjQj3PoDYnWfx@123 --socket=/tmp/mysql.sock --incremental /data/backup/incremental --incremental-basedir=/data/backup/2020-05-07_15-52-09
    
    [root@ 2020-05-07_16-29-46]# du -sh
    3.5M    . # 可以看到备份后的数据很小
    [root@ 2020-05-07_16-29-46]# cat xtrabackup_checkpoints 
    backup_type = incremental
    from_lsn = 1501132450
    to_lsn = 1501136261
    last_lsn = 1501136270
    compact = 0
    recover_binlog_info = 0
    
    # 可以使用--parallel=N 指定同时备份文件的线程数,并行复制
    
    # 我们基于这次的增备,再创建一个增备,如果每天都要创建增备,就可以以此类推
    16:23:12pm> insert into users(email,name) values('test3@com','test3');
    Query OK, 1 row affected (0.00 sec)
    
    16:37:53pm> select * from test.users;                                 
    +----+-----------+-------+---------------------+
    | id | email     | name  | creation_time       |
    +----+-----------+-------+---------------------+
    |  1 | test@com  | test  | 2020-05-07 15:15:35 |
    |  3 | test2@com | test2 | 2020-05-07 16:22:50 |
    |  4 | test3@com | test3 | 2020-05-07 16:37:53 |
    +----+-----------+-------+---------------------+
    3 rows in set (0.00 sec)
    
    [root@ ~]# innobackupex --defaults-file=/etc/my.cnf --user=backup --password=xkjQj3PoDYnWfx@123 --socket=/tmp/mysql.sock --incremental /data/backup/incremental --incremental-basedir=/data/backup/incremental/2020-05-07_16-29-46/
    [root@ ~]# du -sh /data/backup/incremental/2020-05-07_16-41-06/
    3.0M    /data/backup/incremental/2020-05-07_16-41-06/
    
  4. 增备恢复

    # 现在把上面两次的增备和对应的全备恢复了,大致步骤如下:
    # 1. 恢复全备
    # 2. 恢复增备到全备(除最后一个增备外,其他增备都需要--redo-only)
    # 3. 对整体的完全备份,进行恢复,回滚那些未提交的事务
    # 恢复全备
    [root@ ~]# innobackupex --apply-log --redo-only /data/backup/2020-05-07_15-52-09/
    # 将增备1应用到全备
    [root@ ~]# innobackupex --apply-log --redo-only /data/backup/2020-05-07_15-52-09/ --incremental-dir=/data/backup/incremental/2020-05-07_16-29-46/
    # 将增备2应用到全备(此时增备2为最后一个,需要去掉--redo-only,需要回滚xtrabackup_log中未提交的数据)
    [root@ ~]# innobackupex --apply-log /data/backup/2020-05-07_15-52-09/ --incremental-dir=/data/backup/incremental/2020-05-07_16-41-06/
    # 把所有结合再一起的完备,进行一次apply,回滚未提交的数据
    [root@ ~]# innobackupex --apply-log /data/backup/2020-05-07_15-52-09/
    
    # 停掉mysql,移走数据目录,授权,启动,此时省略,下面让我们来检验一下恢复的数据
    
    18:14:25pm> select * from test.users;
    +----+-----------+-------+---------------------+
    | id | email     | name  | creation_time       |
    +----+-----------+-------+---------------------+
    |  1 | test@com  | test  | 2020-05-07 15:15:35 |
    |  3 | test2@com | test2 | 2020-05-07 16:22:50 |
    |  4 | test3@com | test3 | 2020-05-07 16:37:53 |
    +----+-----------+-------+---------------------+
    3 rows in set (0.01 sec)
    
  5. 克隆slave

    # 在我们日常中,有时候需要在线添加从库,如原来的一主一从,需要扩展未一主两从,出于安全考虑,我们通常需要在从库上在线克隆slave,克隆slave时,常用的参数为:--slave-info,--safe-slave-backup
    --slave-info: 会将master的binlog文件名和偏移量的位置保存到xtrabackup_slave_info文件中
    --safe-slave-backup: 会暂停slave的SQL线程知道没有打开的临时表的时候开始备份,备份结束后自动启动SQL线程,这样操作的目的主要时保证一致性的复制状态
    # 克隆slave
    innobackupex --user=root --password=passwd --socket=/tmp/mysqld.sock  --slave-info --safe-slave-backup --no-timestamp /data/backup/cloneslave
    innobackupex: Backup created in directory '/data/backup/cloneslave'
    innobackupex: MySQL binlog position: filename 'mysql-bin.000022', position 107
    innobackupex: MySQL slave binlog position: master host '192.168.88.200', filename 'mysql-bin.000009', position 732
    140413 23:25:13  innobackupex: completed OK!
    
    # xtrabackup_slave_info 就是change master的语句
    cat /data/cloneslave/xtrabackup_slave_info 
    CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000009', MASTER_LOG_POS=732
    # 注意:会自动创建目标目录,无需提前创建
    # 拉取新的从机还原,做主从即可
    

6. 踩过的坑

在一次使用xtarbackup给一个2.5T左右的mysql做物理备份时遇到如下错误

InnoDB: Operating system error number 24 in a file operation.
InnoDB: Error number 24 means 'Too many open files'
InnoDB: Some operating system error numbers are described at http://dev.mysql.com/doc/refman/5.7/en/operating-system-error-codes.html
InnoDB: File ./.......ibd: 'open' returned OS error 124. Cannot continue operation
InnoDB: Cannot continue operation.

问题定位:

  1. 进程支持的最大文件数 xtrabackup: open files limit requested 65535, set to 65536
  2. OS error 124. Cannot continue operation 属于系统级别错误
  3. 检查mysql error日志,并没有报错
  4. 预估应该是系统的open file数量不够xtrabackup进程使用

问题解决

  1. 参考网上的级别都是修改open files,但是和这边业务不是一个量级,错误都是系统错误,单并不是mysql进程级别

  2. 解决方法

    # 会话级别设置ulimit -n
    screen -S xtrabckup_xxxx
    ulimit -n 1048576
    innobackupex XXXXXXXX
    

7. 参考文献