如何使用Ubuntu 16.04上的Percona XtraBackup配置MySQL备份

数据库通常是您的基础设施中最重要的系统之一,因为它们存储的信息的价值。因此,在发生事故或硬件故障的情况下,有可靠的备份来防范数据丢失是非常重要的。珀斯纳

介绍

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

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

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

先决条件

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

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

一旦安装了MySQL,请以sudo用户身份登录到您的服务器继续。

安装Percona Xtrabackup工具

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

要开始,请访问UbuntuPercona发行版,以查找最新的.deb软件包来安装存储库。 由于我们在Ubuntu 16.04,代号为“Xenial Xerus”,我们应该选择“xenial”包。 右键单击相应的链接并复制地址。

注意:您可以随时通过键入以下方式来仔细检查服务器的发行代号:

lsb_release -c
Codename:   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

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

配置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;
+-----------------+
| @@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
/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用户添加到backup组的mysql组和sudo用户:

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

如果我们再次检查/etc/group文件,您将看到您的当前用户被添加到backup组中, backup用户被添加到mysql组中:

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

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

exec su - ${USER}

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

id -nG
sammy 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作为/backups/mysql的基本目录:

sudo mkdir -p /backups/mysql

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

sudo chown backup:mysql /backups/mysql

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

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

由于备份包含数据库系统本身的所有数据,因此重要的是妥善保护它们。 innobackupex实用程序可以对每个文件进行备份和归档时加密。 我们只需要提供一个加密密钥。

我们可以使用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脚本,请在名为backup-mysql.sh/usr/local/bin中创建一个新文件:

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 [ "$USER" != "$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 innobackupex arguments
    #declare -ga innobackupex_args=(
    innobackupex_args=(
        "--defaults-file=${defaults_file}"
        "--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"
        "--incremental"
    )

    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")
        innobackupex_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
    innobackupex "${innobackupex_args[@]}" "${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-前缀表示,表示自上次完全备份或增量备份以来的更改。

备份将在日常目录中生成一个名为backup-progress.log的文件,其中包含最近备份操作的输出。 还将在其中创建一个名为xtrabackup_checkpoints的文件,其中包含最新的备份元数据。 需要此文件来生成未来的增量备份,因此重要的是不要将其删除。

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

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

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}"

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

        innobackupex "${innobackupex_args[@]}" "${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 !=  2 * $# )); 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文件来处理提供的.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}"
    innobackupex --redo-only --apply-log "${full_backup_dir}"

    for increment in "${incremental_dirs[@]}"; do
        printf "Applying incremental backup %s to %s\n" "${increment}" "${full_backup_dir}"
        innobackupex --redo-only --apply-log --incremental-dir="${increment}" "${full_backup_dir}"
    done

    printf "Applying final logs to full backup %s\n" "${full_backup_dir}"
    innobackupex --apply-log "${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} == 2 * (${#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 innobackupex --copy-back ${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
Backup successful!
Backup created at /backups/mysql/Thu/full-04-20-2017_14-55-17.xbstream

如果一切都按计划进行,脚本将正确执行,指示成功,并输出新备份文件的位置。 如上面的输出所示,已经创建了日常目录(在这种情况下为“Thu”)以容纳一天的备份。 备份文件本身以全文开头,表示这是一个完整的备份。

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

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

在这里,我们看到实际的备份文件(在这种情况下为full-04-20-2017_14-55-17.xbstream ),备份事件日志( backup-progress.log )和xtrabackup_checkpoints文件,其中包括有关备份内容。

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

tail backup-progress.log
170420 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
backup_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
Backup successful!
Backup created at /backups/mysql/Thu/incremental-04-20-2017_17-15-03.xbstream

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

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

xtrabackup_checkpoints文件的内容现在引用最近的增量备份:

cat xtrabackup_checkpoints
backup_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
Extraction complete! Backup directories have been extracted to the "restore" directory.

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

如果我们拖尾提取日志,我们可以确认最新的备份是否被成功提取。 文件中较早显示其他备份成功消息。

tail extract-progress.log
170420 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
full-04-20-2017_14-55-17/  incremental-04-20-2017_17-15-03/

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

准备最终备份

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

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

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

sudo -u backup prepare-mysql.sh
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 innobackupex --copy-back /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数据目录

If you are satisfied that everything is in order after reviewing the logs, you can follow the instructions outlined in the prepare-mysql.sh output.

First, stop the running MySQL process:

sudo systemctl stop mysql

Since the backup data may conflict with the current contents of the MySQL data directory, we should remove or move the /var/lib/mysql directory. If you have space on your filesystem, the best option is to move the current contents to the /tmp directory or elsewhere in case something goes wrong:

sudo mv /var/lib/mysql/ /tmp

Recreate an /var/lib/mysql empty directory. We will need to fix permissions in a moment, so we do not need to worry about that yet:

sudo mkdir /var/lib/mysql

Now, we can copy the full backup to the MySQL data directory using the innobackupex utility. Substitute the path to your prepared full backup in the command below:

sudo innobackupex --copy-back /backups/mysql/Thu/restore/full-04-20-2017_14-55-17

A running log of the files being copied will display throughout the process. Once the files are in place, we need to fix the ownership and permissions again so that the MySQL user and group own and can access the restored structure:

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

Our restored files are now in the MySQL data directory.

Start up MySQL again to complete the process:

sudo systemctl start mysql

Check whether the data has been restored by viewing the contents of the playground.equipment table. Again, you will be prompted for the MySQL root password to continue:

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

Our data has been successfully restored.

After restoring your data, it is important to go back and delete the restore directory. Future incremental backups cannot be applied to the full backup once it has been prepared, so we should remove it. Furthermore, the backup directories should not be left unencrypted on disk for security reasons:

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

The next time we need a clean copies of the backup directories, we can extract them again from the backup files.

Creating a Cron Job to Run Backups Hourly

Now that we've verified that the backup and restore process are working smoothly, we should set up a cron job to automatically take regular backups.

We will create a small script within the /etc/cron.hourly directory to automatically run our backup script and log the results. The cron process will automatically run this every hour:

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

Inside, we will call the backup script with the systemd-cat utility so that the output will be available in the journal. We'll mark them with a backup-mysql identifier so we can easily filter the logs:

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

完成后保存并关闭文件。 Make the script executable by typing:

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

The backup script will now run hourly. The script itself will take care of cleaning up backups older than three days ago.

We can test the cron script by running it manually:

sudo /etc/cron.hourly/backup-mysql

After it completes, check the journal for the log messages by typing:

sudo journalctl -t backup-mysql
[scondary_label 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

Check back in a few hours to make sure that additional backups are being taken.

结论

In this guide, we've installed the Percona Xtrabackup tools to help create live snapshots of our MySQL data on a regular basis. We configured a MySQL and system backup user, set up an encryption key to secure our backup files, and then set up scripts to automate parts of the backup and restore procedures.

The backup script generates a full backup at the start of each day and incremental backups every hour afterwards, keeping three days of backups at any time. The encrypted files and the encryption key can be used in conjunction with other backup technologies to transfer the data off-site for safekeeping. The extract and prepare scripts let us assemble the backups for the day into a consistent set of data that can be used to restore the system.