如何在Ubuntu16.04上使用PerconaXtraBackup配置MySQL备份

来自菜鸟教程
跳转至:导航、​搜索

介绍

数据库通常会在您的基础架构中存储一些最有价值的信息。 因此,拥有可靠的备份以防止在发生事故或硬件故障时丢失数据非常重要。

Percona XtraBackup 备份工具提供了一种在系统运行时对 MySQL 数据执行“热”备份的方法。 他们通过在文件系统级别复制数据文件然后执行崩溃恢复来实现数据集内的一致性来做到这一点。

在本指南中,我们将创建一个系统来自动备份 Ubuntu 16.04 服务器上的 MySQL 数据。 我们将在一组脚本中使用 cron 和 Percona 工具来创建定期、安全的备份,以便在出现问题时用于恢复。

先决条件

要完成本指南,您需要一个 Ubuntu 16.04 服务器,并为管理任务配置非 root sudo 用户。 您可以按照我们的 “使用 Ubuntu 16.04 进行初始服务器设置” 指南在您的服务器上设置具有这些权限的用户。

一旦您有 sudo 用户可用,您将需要安装 MySQL。 可以使用这些指南中的任何一个,具体取决于您要使用的包。 如果您想坚持使用官方 Ubuntu 存储库,则第一个指南是合适的,而如果您需要更多最新功能,则第二个指南更适合:

安装 MySQL 后,以 sudo 用户身份登录您的服务器以继续。

安装 Percona Xtrabackup 工具

我们需要做的第一件事是安装实际的 Percona 备份实用程序。 该项目维护自己的存储库,我们可以将其添加到 MySQL 服务器以访问包。

首先,转到 Ubuntu Percona 发布页面,找到用于安装存储库的最新 .deb 软件包。 由于我们使用的是代号为“Xenial Xerus”的Ubuntu 16.04,我们应该选择“xenial”包。 右键单击相应的链接并复制地址。

注意: 您可以随时通过键入以下命令仔细检查服务器的发布代号:

lsb_release -c
OutputCodename:   xenial

复制链接后,移动到 /tmp 目录,然后使用 curl 下载存储库配置包:

cd /tmp
curl -LO https://repo.percona.com/apt/percona-release_0.1-4.xenial_all.deb

接下来,使用 dpkg 安装下载的包,这将在系统上配置 Percona apt 存储库:

sudo dpkg -i percona*

配置新的存储库后,我们将更新本地包索引以获取有关新可用包的信息。 然后,我们将从存储库中安装 XtraBackup 工具和 qpress 压缩实用程序:

sudo apt-get update
sudo apt-get install percona-xtrabackup-24 qpress

在其他捆绑的实用程序中,xtrabackupxbstreamqpress 命令现在将可用。 我们的脚本将使用其中的每一个来执行备份和恢复数据。

配置 MySQL 备份用户并添加测试数据

首先,使用 MySQL root 用户启动一个交互式 MySQL 会话:

mysql -u root -p

系统将提示您输入在 MySQL 安装期间选择的管理密码。 输入密码后,您将进入 MySQL 会话。

创建具有适当权限的 MySQL 用户

我们需要做的第一件事是创建一个配置为处理备份任务的新 MySQL 用户。 我们只会给予该用户在系统运行时安全复制数据所需的权限。

为了明确帐户的用途,我们将调用新用户 backup。 我们将把用户的凭据放在一个安全的文件中,所以请随意选择一个复杂的密码:

CREATE USER 'backup'@'localhost' IDENTIFIED BY 'password';

接下来,我们需要授予新的 backup 用户在数据库系统上执行所有备份操作所需的权限。 通过键入以下内容授予所需的权限并将它们应用于当前会话:

GRANT RELOAD, LOCK TABLES, REPLICATION CLIENT, CREATE TABLESPACE, PROCESS, SUPER, CREATE, INSERT, SELECT ON *.* TO 'backup'@'localhost';
FLUSH PRIVILEGES;

我们的 MySQL 备份用户已配置并具有所需的访问权限。

为备份创建测试数据

接下来,我们将创建一些测试数据。 运行以下命令以创建带有 equipment 表的 playground 数据库。 我们将从插入代表蓝色幻灯片的单个记录开始:

CREATE DATABASE playground;
CREATE TABLE playground.equipment ( id INT NOT NULL AUTO_INCREMENT, type VARCHAR(50), quant INT, color VARCHAR(25), PRIMARY KEY(id));
INSERT INTO playground.equipment (type, quant, color) VALUES ("slide", 2, "blue");

在本指南的后面部分,我们将使用和更改这些数据来测试我们创建完整备份和增量备份的能力。

在结束 MySQL 会话之前,我们将检查 datadir 变量的值。 我们需要知道这个值,以确保我们的系统级 backup 用户可以访问 MySQL 数据文件。

通过键入以下内容显示 datadir 变量的值:

SELECT @@datadir;
Output+-----------------+
| @@datadir       |
+-----------------+
| /var/lib/mysql/ |
+-----------------+
1 row in set (0.01 sec)

记下您找到的位置。

这就是我们目前在 MySQL 中需要做的所有事情。 通过键入以下内容退出到 shell:

exit

接下来,我们可以看看一些系统级别的配置。

配置系统备份用户并分配权限

现在我们有一个 MySQL 用户来执行备份,我们将确保相应的 Linux 用户存在,具有类似的有限权限。

在 Ubuntu 16.04 上,backup 用户和相应的 backup 组已经可用。 通过使用以下命令检查 /etc/passwd/etc/group 文件来确认这一点:

grep backup /etc/passwd /etc/group
Output/etc/passwd:backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
/etc/group:backup:x:34:

/etc/passwd 文件的第一行描述了 backup 用户,而 /etc/group 文件的第二行定义了 backup 组。

保存 MySQL 数据的 /var/lib/mysql 目录归 mysql 用户和组所有。 我们可以将 backup 用户添加到 mysql 组,以安全地允许访问数据库文件和目录。 我们还应该将我们的 sudo 用户添加到 backup 组,以便我们可以访问我们将备份的文件。

键入以下命令将 backup 用户添加到 mysql 组,并将您的 sudo 用户添加到 backup 组:

sudo usermod -aG mysql backup
sudo usermod -aG backup ${USER}

如果我们再次检查 /etc/group 文件,您会看到您的当前用户已添加到 backup 组,并且 backup 用户已添加到 [X155X ] 团体:

grep backup /etc/group
Outputbackup:x:34:sammy
mysql:x:116:backup

新组在我们当前的会话中自动不可用。 要重新评估我们的 sudo 用户可用的组,请注销并重新登录,或键入:

exec su - ${USER}

系统将提示您输入 sudo 用户密码以继续。 通过再次检查我们的用户组,确认您当前的会话现在可以访问 backup 组:

id -nG
Outputsammy sudo backup

我们的 sudo 用户现在可以利用其在 backup 组中的成员资格。

接下来,我们需要通过添加组执行权限,使 mysql 组可以访问 /var/lib/mysql 目录及其子目录。 否则,backup 用户将无法进入这些目录,即使它是 mysql 组的成员。

注意: 如果您之前在 MySQL 内部检查时 datadir 的值不是 /var/lib/mysql,请替换您在以下命令中发现的目录。


要授予 mysql 组访问 MySQL 数据目录的权限,请键入:

sudo find /var/lib/mysql -type d -exec chmod 750 {} \;

我们的 backup 用户现在可以访问 MySQL 目录。

创建备份资产

现在 MySQL 和系统备份用户可用,我们可以开始设置成功创建和保护备份所需的配置文件、加密密钥和其他资产。

使用备份参数创建 MySQL 配置文件

首先创建备份脚本将使用的最小 MySQL 配置文件。 这将包含 MySQL 用户的 MySQL 凭据。

在文本编辑器中打开位于 /etc/mysql/backup.cnf 的文件:

sudo nano /etc/mysql/backup.cnf

在里面,开始一个 [client] 部分并设置您在 MySQL 中定义的 MySQL 备份用户和密码用户:

/etc/mysql/backup.cnf

[client]
user=backup
password=password

完成后保存并关闭文件。

将文件的所有权授予 backup 用户,然后限制权限,使其他用户无法访问该文件:

sudo chown backup /etc/mysql/backup.cnf
sudo chmod 600 /etc/mysql/backup.cnf

备份用户将能够访问此文件以获取正确的凭据,但其他用户将受到限制。

创建备份根目录

接下来,为备份内容创建一个目录。 我们将使用 /backups/mysql 作为备份的基本目录:

sudo mkdir -p /backups/mysql

接下来,将 /backups/mysql 目录的所有权分配给 backup 用户,并将组所有权分配给 mysql 组:

sudo chown backup:mysql /backups/mysql

backup 用户现在应该能够将备份数据写入此位置。

创建加密密钥以保护备份文件

因为备份包含来自数据库系统本身的所有数据,所以正确保护它们很重要。 xtrabackup 实用程序能够在备份和存档每个文件时对其进行加密。 我们只需要为它提供一个加密密钥。

我们可以使用 openssl 命令在备份根目录中创建加密密钥:

printf '%s' "$(openssl rand -base64 24)" | sudo tee /backups/mysql/encryption_key && echo

限制对该文件的访问也非常重要。 同样,将所有权分配给 backup 用户并拒绝所有其他用户的访问:

sudo chown backup:backup /backups/mysql/encryption_key
sudo chmod 600 /backups/mysql/encryption_key

此密钥将在备份过程中以及您需要从备份中恢复的任何时间使用。

创建备份和恢复脚本

我们现在拥有对正在运行的 MySQL 实例执行安全备份所需的一切。

为了使我们的备份和恢复步骤可重复,我们将编写整个过程的脚本。 我们将创建以下脚本:

  • backup-mysql.sh:此脚本备份 MySQL 数据库,在此过程中对文件进行加密和压缩。 它创建完整和增量备份,并按天自动组织内容。 默认情况下,该脚本会维护 3 天的备份。
  • extract-mysql.sh:此脚本对备份文件进行解压缩和解密,以创建包含备份内容的目录。
  • prepare-mysql.sh:此脚本通过处理文件和应用日志来“准备”备份目录。 任何增量备份都将应用于完整备份。 准备脚本完成后,文件就可以移回数据目录了。

您可以随时在 GitHub 上查看本教程的 存储库中的脚本。 如果您不想复制和粘贴以下内容,可以直接从 GitHub 下载,输入:

cd /tmp
curl -LO https://raw.githubusercontent.com/do-community/ubuntu-1604-mysql-backup/master/backup-mysql.sh
curl -LO https://raw.githubusercontent.com/do-community/ubuntu-1604-mysql-backup/master/extract-mysql.sh
curl -LO https://raw.githubusercontent.com/do-community/ubuntu-1604-mysql-backup/master/prepare-mysql.sh

请务必在下载后检查脚本,以确保它们已成功检索并且您批准它们将执行的操作。 如果您满意,请将脚本标记为可执行,然后通过键入以下命令将它们移动到 /usr/local/bin 目录中:

chmod +x /tmp/{backup,extract,prepare}-mysql.sh
sudo mv /tmp/{backup,extract,prepare}-mysql.sh /usr/local/bin

接下来,我们将设置每个脚本并更详细地讨论它们。

创建 backup-mysql.sh 脚本

如果您没有从 GitHub 下载 backup-mysql.sh 脚本,请在 /usr/local/bin 目录中创建一个名为 backup-mysql.sh 的新文件:

sudo nano /usr/local/bin/backup-mysql.sh

将脚本内容复制并粘贴到文件中:

/usr/local/bin/backup-mysql.sh

#!/bin/bash

export LC_ALL=C

days_of_backups=3  # Must be less than 7
backup_owner="backup"
parent_dir="/backups/mysql"
defaults_file="/etc/mysql/backup.cnf"
todays_dir="${parent_dir}/$(date +%a)"
log_file="${todays_dir}/backup-progress.log"
encryption_key_file="${parent_dir}/encryption_key"
now="$(date +%m-%d-%Y_%H-%M-%S)"
processors="$(nproc --all)"

# Use this to echo to standard error
error () {
    printf "%s: %s\n" "$(basename "${BASH_SOURCE}")" "${1}" >&2
    exit 1
}

trap 'error "An unexpected error occurred."' ERR

sanity_check () {
    # Check user running the script
    if [ "$(id --user --name)" != "$backup_owner" ]; then
        error "Script can only be run as the \"$backup_owner\" user"
    fi
    
    # Check whether the encryption key file is available
    if [ ! -r "${encryption_key_file}" ]; then
        error "Cannot read encryption key at ${encryption_key_file}"
    fi
}

set_options () {
    # List the xtrabackup arguments
    xtrabackup_args=(
        "--defaults-file=${defaults_file}"
        "--backup"
        "--extra-lsndir=${todays_dir}"
        "--compress"
        "--stream=xbstream"
        "--encrypt=AES256"
        "--encrypt-key-file=${encryption_key_file}"
        "--parallel=${processors}"
        "--compress-threads=${processors}"
        "--encrypt-threads=${processors}"
        "--slave-info"
    )
    
    backup_type="full"

    # Add option to read LSN (log sequence number) if a full backup has been
    # taken today.
    if grep -q -s "to_lsn" "${todays_dir}/xtrabackup_checkpoints"; then
        backup_type="incremental"
        lsn=$(awk '/to_lsn/ {print $3;}' "${todays_dir}/xtrabackup_checkpoints")
        xtrabackup_args+=( "--incremental-lsn=${lsn}" )
    fi
}

rotate_old () {
    # Remove the oldest backup in rotation
    day_dir_to_remove="${parent_dir}/$(date --date="${days_of_backups} days ago" +%a)"

    if [ -d "${day_dir_to_remove}" ]; then
        rm -rf "${day_dir_to_remove}"
    fi
}

take_backup () {
    # Make sure today's backup directory is available and take the actual backup
    mkdir -p "${todays_dir}"
    find "${todays_dir}" -type f -name "*.incomplete" -delete
    xtrabackup "${xtrabackup_args[@]}" --target-dir="${todays_dir}" > "${todays_dir}/${backup_type}-${now}.xbstream.incomplete" 2> "${log_file}"
    
    mv "${todays_dir}/${backup_type}-${now}.xbstream.incomplete" "${todays_dir}/${backup_type}-${now}.xbstream"
}

sanity_check && set_options && rotate_old && take_backup

# Check success and print message
if tail -1 "${log_file}" | grep -q "completed OK"; then
    printf "Backup successful!\n"
    printf "Backup created at %s/%s-%s.xbstream\n" "${todays_dir}" "${backup_type}" "${now}"
else
    error "Backup failure! Check ${log_file} for more information"
fi

该脚本具有以下功能:

  • 每天第一次运行时创建一个加密、压缩的完整备份。
  • 在同一天再次调用时,根据每日完整备份生成加密的压缩增量备份。
  • 维护按天组织的备份。 默认情况下,会保留三天的备份。 这可以通过调整脚本中的 days_of_backups 参数来更改。

运行脚本时,将创建一个每日目录,其中将写入代表单个备份的时间戳文件。 第一个时间戳文件将是完整备份,前缀为 full-。 当天的后续备份将是增量备份,由 incremental- 前缀表示,表示自上次完整或增量备份以来的更改。

备份将在 daily 目录中生成一个名为 backup-progress.log 的文件,其中包含最近备份操作的输出。 还将在那里创建一个名为 xtrabackup_checkpoints 的文件,其中包含最新的备份元数据。 该文件是生成未来增量备份所必需的,因此不要删除它很重要。 还会生成一个名为 xtrabackup_info 的文件,其中包含其他元数据,但脚本不引用此文件。

完成后,保存并关闭文件。

接下来,如果您还没有这样做,请键入以下命令使脚本可执行:

sudo chmod +x /usr/local/bin/backup-mysql.sh

我们现在有一个可用的命令来启动 MySQL 备份。

创建 extract-mysql.sh 脚本

接下来,我们将创建 extract-mysql.sh 脚本。 这将用于从单个备份文件中提取 MySQL 数据目录结构。

如果您没有从存储库下载脚本,请在 /usr/local/bin 目录中创建并打开一个名为 extract-mysql.sh 的文件:

sudo nano /usr/local/bin/extract-mysql.sh

在里面,粘贴以下脚本:

/usr/local/bin/extract-mysql.sh

#!/bin/bash

export LC_ALL=C

backup_owner="backup"
encryption_key_file="/backups/mysql/encryption_key"
log_file="extract-progress.log"
number_of_args="${#}"
processors="$(nproc --all)"

# Use this to echo to standard error
error () {
    printf "%s: %s\n" "$(basename "${BASH_SOURCE}")" "${1}" >&2
    exit 1
}

trap 'error "An unexpected error occurred.  Try checking the \"${log_file}\" file for more information."' ERR

sanity_check () {
    # Check user running the script
    if [ "${USER}" != "${backup_owner}" ]; then
        error "Script can only be run as the \"${backup_owner}\" user"
    fi
    
    # Check whether the qpress binary is installed
    if ! command -v qpress >/dev/null 2>&1; then
        error "Could not find the \"qpress\" command.  Please install it and try again."
    fi
    
    # Check whether any arguments were passed
    if [ "${number_of_args}" -lt 1 ]; then
        error "Script requires at least one \".xbstream\" file as an argument."
    fi
    
    # Check whether the encryption key file is available
    if [ ! -r "${encryption_key_file}" ]; then
        error "Cannot read encryption key at ${encryption_key_file}"
    fi
}

do_extraction () {
    for file in "${@}"; do
        base_filename="$(basename "${file%.xbstream}")"
        restore_dir="./restore/${base_filename}"
    
        printf "\n\nExtracting file %s\n\n" "${file}"
    
        # Extract the directory structure from the backup file
        mkdir --verbose -p "${restore_dir}"
        xbstream -x -C "${restore_dir}" < "${file}"

        xtrabackup_args=(
            "--parallel=${processors}"
            "--decrypt=AES256"
            "--encrypt-key-file=${encryption_key_file}"
            "--decompress"
        )

        xtrabackup "${xtrabackup_args[@]}" --target-dir="${restore_dir}"
        find "${restore_dir}" -name "*.xbcrypt" -exec rm {} \;
        find "${restore_dir}" -name "*.qp" -exec rm {} \;
    
        printf "\n\nFinished work on %s\n\n" "${file}"
    
    done > "${log_file}" 2>&1
}

sanity_check && do_extraction "$@"

ok_count="$(grep -c 'completed OK' "${log_file}")"

# Check the number of reported completions.  For each file, there is an
# informational "completed OK".  If the processing was successful, an
# additional "completed OK" is printed. Together, this means there should be 2
# notices per backup file if the process was successful.
if (( $ok_count !=  $# )); then
    error "It looks like something went wrong. Please check the \"${log_file}\" file for additional information"
else
    printf "Extraction complete! Backup directories have been extracted to the \"restore\" directory.\n"
fi

与设计为自动化的 backup-mysql.sh 脚本不同,此脚本旨在在您计划从备份恢复时有意使用。 因此,该脚本希望您传递您希望提取的 .xbstream 文件。

该脚本在当前目录中创建一个 restore 目录,然后为作为参数传入的每个备份创建单独的目录。 它将通过从存档中提取目录结构,解密其中的各个文件,然后解压缩解密文件来处理提供的 .xbstream 文件。

此过程完成后,restore 目录应包含每个提供的备份的目录。 这允许您检查目录、检查备份的内容,并决定您希望准备和恢复哪些备份。

完成后保存并关闭文件。 之后,通过键入以下命令确保脚本是可执行的:

sudo chmod +x /usr/local/bin/extract-mysql.sh

该脚本将允许我们将单个备份文件展开到恢复所需的目录结构中。

创建 prepare-mysql.sh 脚本

最后,在 /usr/local/bin 目录下下载或创建 prepare-mysql.sh 脚本。 此脚本会将日志应用到每个备份以创建一致的数据库快照。 它将任何增量备份应用于完整备份以合并以后的更改。

如果您之前没有下载脚本文件,请在文本编辑器中创建它:

sudo nano /usr/local/bin/prepare-mysql.sh

在里面,粘贴以下内容:

/usr/local/bin/prepare-mysql.sh

#!/bin/bash

export LC_ALL=C

shopt -s nullglob
incremental_dirs=( ./incremental-*/ )
full_dirs=( ./full-*/ )
shopt -u nullglob

backup_owner="backup"
log_file="prepare-progress.log"
full_backup_dir="${full_dirs[0]}"

# Use this to echo to standard error
error() {
    printf "%s: %s\n" "$(basename "${BASH_SOURCE}")" "${1}" >&2
    exit 1
}

trap 'error "An unexpected error occurred.  Try checking the \"${log_file}\" file for more information."' ERR

sanity_check () {
    # Check user running the script
    if [ "${USER}" != "${backup_owner}" ]; then
        error "Script can only be run as the \"${backup_owner}\" user."
    fi

    # Check whether a single full backup directory are available
    if (( ${#full_dirs[@]} != 1 )); then
        error "Exactly one full backup directory is required."
    fi
}

do_backup () {
    # Apply the logs to each of the backups
    printf "Initial prep of full backup %s\n" "${full_backup_dir}"
    xtrabackup --prepare --apply-log-only --target-dir="${full_backup_dir}"
    
    for increment in "${incremental_dirs[@]}"; do
        printf "Applying incremental backup %s to %s\n" "${increment}" "${full_backup_dir}"
        xtrabackup --prepare --apply-log-only --incremental-dir="${increment}" --target-dir="${full_backup_dir}"
    done
    
    printf "Applying final logs to full backup %s\n" "${full_backup_dir}"
    xtrabackup --prepare --target-dir="${full_backup_dir}"
}

sanity_check && do_backup > "${log_file}" 2>&1

# Check the number of reported completions.  Each time a backup is processed,
# an informational "completed OK" and a real version is printed.  At the end of
# the process, a final full apply is performed, generating another 2 messages.
ok_count="$(grep -c 'completed OK' "${log_file}")"

if (( ${ok_count} == ${#full_dirs[@]} + ${#incremental_dirs[@]} + 1 )); then
    cat << EOF
Backup looks to be fully prepared.  Please check the "prepare-progress.log" file
to verify before continuing.

If everything looks correct, you can apply the restored files.

First, stop MySQL and move or remove the contents of the MySQL data directory:
    
        sudo systemctl stop mysql
        sudo mv /var/lib/mysql/ /tmp/
    
Then, recreate the data directory and  copy the backup files:
    
        sudo mkdir /var/lib/mysql
        sudo xtrabackup --copy-back --target-dir=${PWD}/$(basename "${full_backup_dir}")
    
Afterward the files are copied, adjust the permissions and restart the service:
    
        sudo chown -R mysql:mysql /var/lib/mysql
        sudo find /var/lib/mysql -type d -exec chmod 750 {} \\;
        sudo systemctl start mysql
EOF
else
    error "It looks like something went wrong.  Check the \"${log_file}\" file for more information."
fi

该脚本在当前目录中查找以 full-incremental- 开头的目录。 它使用 MySQL 日志将提交的事务应用于完整备份。 之后,它将任何增量备份应用于完整备份,以使用更新的信息更新数据,再次应用提交的事务。

合并所有备份后,将回滚未提交的事务。 此时,full- 备份将代表一组一致的数据,可以移动到 MySQL 的数据目录中。

为了最大程度地减少数据丢失的可能性,该脚本不会将文件复制到数据目录中。 这样,用户可以手动验证备份内容和在此过程中创建的日志文件,并决定如何处理 MySQL 数据目录的当前内容。 命令退出时会显示完全恢复文件所需的命令。

完成后保存并关闭文件。 如果您之前没有这样做,请通过键入以下命令将文件标记为可执行文件:

sudo chmod +x /usr/local/bin/prepare-mysql.sh

这个脚本是我们在将备份文件移动到 MySQL 的数据目录之前运行的最后一个脚本。

测试 MySQL 备份和恢复脚本

现在备份和恢复脚本在服务器上,我们应该测试它们。

执行完整备份

首先使用 backup 用户调用 backup-mysql.sh 脚本:

sudo -u backup backup-mysql.sh
OutputBackup successful!
Backup created at /backups/mysql/Thu/full-04-20-2017_14-55-17.xbstream

如果一切按计划进行,脚本将正确执行,指示成功,并输出新备份文件的位置。 如上面的输出所示,已经创建了一个每日目录(在本例中为“Thu”)来存放当天的备份。 备份文件本身以 full- 开头,表示这是一个完整备份。

让我们进入每日备份目录并查看内容:

cd /backups/mysql/"$(date +%a)"
ls
Outputbackup-progress.log  full-04-20-2017_14-55-17.xbstream  xtrabackup_checkpoints  xtrabackup_info

在这里,我们看到了实际的备份文件(本例中为 full-04-20-2017_14-55-17.xbstream)、备份事件的日志(backup-progress.log)、xtrabackup_checkpoints 文件,其中包含有关备份的元数据up 内容,以及包含附加元数据的 xtrabackup_info 文件。

如果我们跟踪 backup-progress.log,我们可以确认备份成功完成。

tail backup-progress.log
Output170420 14:55:19 All tables unlocked
170420 14:55:19 [00] Compressing, encrypting and streaming ib_buffer_pool to <STDOUT>
170420 14:55:19 [00]        ...done
170420 14:55:19 Backup created in directory '/backups/mysql/Thu/'
170420 14:55:19 [00] Compressing, encrypting and streaming backup-my.cnf
170420 14:55:19 [00]        ...done
170420 14:55:19 [00] Compressing, encrypting and streaming xtrabackup_info
170420 14:55:19 [00]        ...done
xtrabackup: Transaction log of lsn (2549956) to (2549965) was copied.
170420 14:55:19 completed OK!

如果我们查看 xtrabackup_checkpoints 文件,我们可以查看有关备份的信息。 虽然此文件提供了一些对管理员有用的信息,但它主要用于后续备份作业,以便他们知道哪些数据已被处理。

这是包含在每个存档中的文件的副本。 即使此副本被每个备份覆盖以表示最新信息,但每个原始文件仍将在备份存档中可用。

cat xtrabackup_checkpoints
Outputbackup_type = full-backuped
from_lsn = 0
to_lsn = 2549956
last_lsn = 2549965
compact = 0
recover_binlog_info = 0

上面的示例告诉我们进行了完整备份,并且备份涵盖了日志序列号 (LSN) 0 到日志序列号 2549956。 last_lsn编号表示备份过程中发生了一些操作。

执行增量备份

现在我们有了完整备份,我们可以进行额外的增量备份。 增量备份记录自上次执行备份以来所做的更改。 第一次增量备份基于完整备份,后续增量备份基于之前的增量备份。

在进行另一个备份之前,我们应该将一些数据添加到我们的数据库中,以便我们可以知道哪些备份已被应用。

在我们的 playground 数据库的 equipment 表中插入另一条记录,代表 10 个黄色波动。 在此过程中,系统将提示您输入 MySQL 管理密码:

mysql -u root -p -e 'INSERT INTO playground.equipment (type, quant, color) VALUES ("swing", 10, "yellow");'

既然当前数据比我们最近的备份多,我们可以进行增量备份来捕获更改。 如果存在同一天的完整备份,则 backup-mysql.sh 脚本将进行增量备份:

sudo -u backup backup-mysql.sh
OutputBackup successful!
Backup created at /backups/mysql/Thu/incremental-04-20-2017_17-15-03.xbstream

再次检查每日备份目录以找到增量备份存档:

cd /backups/mysql/"$(date +%a)"
ls
Outputbackup-progress.log                incremental-04-20-2017_17-15-03.xbstream  xtrabackup_info
full-04-20-2017_14-55-17.xbstream  xtrabackup_checkpoints

xtrabackup_checkpoints 文件的内容现在指的是最近的增量备份:

cat xtrabackup_checkpoints
Outputbackup_type = incremental
from_lsn = 2549956
to_lsn = 2550159
last_lsn = 2550168
compact = 0
recover_binlog_info = 0

备份类型被列为“增量”,而不是像我们的完整备份那样从 LSN 0 开始,它从我们上次备份结束的 LSN 开始。

提取备份

接下来,让我们提取备份文件以创建备份目录。 出于空间和安全方面的考虑,这通常只应在您准备好恢复数据时进行。

我们可以通过将 .xbstream 备份文件传递给 extract-mysql.sh 脚本来提取备份。 同样,这必须由 backup 用户运行:

sudo -u backup extract-mysql.sh *.xbstream
OutputExtraction complete! Backup directories have been extracted to the "restore" directory.

以上输出表明该过程已成功完成。 如果我们再次查看每日备份目录的内容,则会创建一个extract-progress.log文件和一个restore目录。

如果我们跟踪提取日志,我们可以确认最新备份已成功提取。 其他备份成功消息显示在文件的前面。

tail extract-progress.log
Output170420 17:23:32 [01] decrypting and decompressing ./performance_schema/socket_instances.frm.qp.xbcrypt
170420 17:23:32 [01] decrypting and decompressing ./performance_schema/events_waits_summary_by_user_by_event_name.frm.qp.xbcrypt
170420 17:23:32 [01] decrypting and decompressing ./performance_schema/status_by_user.frm.qp.xbcrypt
170420 17:23:32 [01] decrypting and decompressing ./performance_schema/replication_group_members.frm.qp.xbcrypt
170420 17:23:32 [01] decrypting and decompressing ./xtrabackup_logfile.qp.xbcrypt
170420 17:23:33 completed OK!


Finished work on incremental-04-20-2017_17-15-03.xbstream

如果我们移动到 restore 目录,与我们提取的备份文件对应的目录现在可用:

cd restore
ls -F
Outputfull-04-20-2017_14-55-17/  incremental-04-20-2017_17-15-03/

备份目录包含原始备份文件,但它们尚未处于 MySQL 可以使用的状态。 为了解决这个问题,我们需要准备文件。

准备最终备份

接下来,我们将准备备份文件。 为此,您必须位于包含 full- 和任何 incremental- 备份的 restore 目录中。 该脚本会将任何 incremental- 目录中的更改应用到 full- 备份目录。 之后,它将应用日志来创建 MySQL 可以使用的一致数据集。

如果出于任何原因您不想恢复某些更改,现在是您从 restore 目录中删除这些增量备份目录的最后机会(增量备份文件仍将在父目录中可用) . 当前目录中剩余的 incremental- 目录将应用于 full- 备份目录。

准备好后,调用 prepare-mysql.sh 脚本。 同样,确保您位于各个备份目录所在的 restore 目录中:

sudo -u backup prepare-mysql.sh
OutputBackup looks to be fully prepared.  Please check the "prepare-progress.log" file
to verify before continuing.

If everything looks correct, you can apply the restored files.

First, stop MySQL and move or remove the contents of the MySQL data directory:
    
        sudo systemctl stop mysql
        sudo mv /var/lib/mysql/ /tmp/
    
Then, recreate the data directory and  copy the backup files:
    
        sudo mkdir /var/lib/mysql
        sudo xtrabackup --copy-back --target-dir=/backups/mysql/Thu/restore/full-04-20-2017_14-55-17
    
Afterward the files are copied, adjust the permissions and restart the service:
    
        sudo chown -R mysql:mysql /var/lib/mysql
        sudo find /var/lib/mysql -type d -exec chmod 750 {} \;
        sudo systemctl start mysql

上面的输出表明脚本认为备份已完全准备好,并且 full- 备份现在表示完全一致的数据集。 如输出所示,您应该检查 prepare-progress.log 文件以确认在此过程中没有报告错误。

该脚本实际上没有将文件复制到 MySQL 的数据目录中,以便您可以验证一切看起来是否正确。

将备份数据恢复到 MySQL 数据目录

如果您在查看日志后对一切正常感到满意,则可以按照 prepare-mysql.sh 输出中列出的说明进行操作。

首先,停止正在运行的 MySQL 进程:

sudo systemctl stop mysql

由于备份的数据可能与 MySQL 数据目录的当前内容冲突,我们应该删除或移动 /var/lib/mysql 目录。 如果文件系统上有空间,最好的选择是将当前内容移动到 /tmp 目录或其他地方,以防出现问题:

sudo mv /var/lib/mysql/ /tmp

重新创建一个空的 /var/lib/mysql 目录。 我们需要马上修复权限,所以我们还不需要担心:

sudo mkdir /var/lib/mysql

现在,我们可以使用 xtrabackup 实用程序将完整备份复制到 MySQL 数据目录。 在以下命令中替换您准备好的完整备份的路径:

sudo xtrabackup --copy-back --target-dir=/backups/mysql/Thu/restore/full-04-20-2017_14-55-17

正在复制的文件的运行日志将在整个过程中显示。 一旦文件到位,我们需要再次修复所有权和权限,以便 MySQL 用户和组拥有并可以访问恢复的结构:

sudo chown -R mysql:mysql /var/lib/mysql
sudo find /var/lib/mysql -type d -exec chmod 750 {} \;

我们恢复的文件现在位于 MySQL 数据目录中。

再次启动 MySQL 以完成该过程:

sudo systemctl start mysql

通过查看playground.equipment表的内容来检查数据是否已经恢复。 同样,系统将提示您输入 MySQL root 密码以继续:

mysql -u root -p -e 'SELECT * FROM playground.equipment;'
Output+----+-------+-------+--------+
| id | type  | quant | color  |
+----+-------+-------+--------+
|  1 | slide |     2 | blue   |
|  2 | swing |    10 | yellow |
+----+-------+-------+--------+
2 rows in set (0.02 sec)

我们的数据已成功恢复。

恢复数据后,请务必返回并删除 restore 目录。 未来的增量备份一旦准备好就无法应用于完整备份,因此我们应该将其删除。 此外,出于安全原因,备份目录不应在磁盘上未加密:

cd ~
sudo rm -rf /backups/mysql/"$(date +%a)"/restore

下次我们需要备份目录的干净副本时,我们可以再次从备份文件中提取它们。

创建 Cron 作业以每小时运行一次备份

现在我们已经验证了备份和恢复过程运行顺利,我们应该设置一个 cron 作业来自动进行定期备份。

我们将在 /etc/cron.hourly 目录中创建一个小脚本来自动运行我们的备份脚本并记录结果。 cron 进程将每小时自动运行一次:

sudo nano /etc/cron.hourly/backup-mysql

在内部,我们将使用 systemd-cat 实用程序调用备份脚本,以便输出在日志中可用。 我们将用 backup-mysql 标识符标记它们,以便我们可以轻松过滤日志:

/etc/cron.hourly/backup-mysql

#!/bin/bash
sudo -u backup systemd-cat --identifier=backup-mysql /usr/local/bin/backup-mysql.sh

完成后保存并关闭文件。 通过键入以下命令使脚本可执行:

sudo chmod +x /etc/cron.hourly/backup-mysql

备份脚本现在将每小时运行一次。 该脚本本身将负责清理三天前的备份。

我们可以通过手动运行来测试 cron 脚本:

sudo /etc/cron.hourly/backup-mysql

完成后,通过键入以下内容检查日志中的日志消息:

sudo journalctl -t backup-mysql
Output-- Logs begin at Wed 2017-04-19 18:59:23 UTC, end at Thu 2017-04-20 18:54:49 UTC. --
Apr 20 18:35:07 myserver backup-mysql[2302]: Backup successful!
Apr 20 18:35:07 myserver backup-mysql[2302]: Backup created at /backups/mysql/Thu/incremental-04-20-2017_18-35-05.xbstream

请过几个小时再回来检查以确保正在执行其他备份。

结论

在本指南中,我们安装了 Percona Xtrabackup 工具来帮助定期创建 MySQL 数据的实时快照。 我们配置了 MySQL 和系统备份用户,设置了加密密钥来保护我们的备份文件,然后设置脚本来自动化部分备份和恢复过程。

备份脚本在每天开始时生成完整备份,之后每小时生成增量备份,随时保留三天的备份。 加密文件和加密密钥可与其他备份技术结合使用,将数据传输到异地以进行安全保存。 提取和准备脚本让我们将当天的备份组合成一组一致的数据,可用于恢复系统。