跳转到内容

备份与恢复演练

备份只有在能成功恢复的时候才有价值。许多团队在真正需要恢复数据时才发现备份损坏、不完整或恢复流程不可执行。定期的备份恢复演练是确保业务连续性的关键实践。

风险后果演练如何帮助
备份文件损坏无法恢复任何数据验证备份完整性
恢复流程未文档化故障时手忙脚乱,恢复时间过长形成标准操作手册
备份数据不完整关键数据缺失发现遗漏的备份对象
人员变动只有特定人员会操作恢复团队成员都能执行
环境差异备份在测试环境无法恢复暴露环境兼容性问题

准备一台与生产环境配置相似的测试服务器:

Terminal window
# 测试服务器基本信息确认
cat /etc/redhat-release
uname -r
df -h
free -h

确保测试环境安装了必要的恢复工具:

Terminal window
dnf install -y \
borgbackup \
mysql \
postgresql \
tar \
rsync \
gzip \
xz

记录需要验证的备份源:

Terminal window
# 列出可用的备份存档
# Borg 备份
borg list /backup/borg-repo
# 或列出远程备份
borg list ssh://backup@backup-server/repo
# 记录最新备份的时间戳
borg info /backup/borg-repo::latest

在恢复之前,先确认备份文件本身是完整的。

Terminal window
# 校验仓库完整性
borg check /backup/borg-repo
# 校验特定存档
borg check --archives-only /backup/borg-repo::2026-03-24_0200
# 详细校验(包含数据块验证,耗时较长)
borg check --verify-data /backup/borg-repo
Terminal window
# 测试 tar 包完整性
tar -tzf /backup/full-backup-20260324.tar.gz > /dev/null
echo "Exit code: $?"
# Exit code 为 0 表示正常
# 如果使用了 gpg 加密
gpg --decrypt /backup/full-backup-20260324.tar.gz.gpg | tar -tz > /dev/null
Terminal window
# 对比源和备份的文件差异
rsync -avnc /source/path/ /backup/rsync-mirror/ > /tmp/rsync-diff.txt
cat /tmp/rsync-diff.txt
# 差异为空则表示一致
Terminal window
# 创建恢复临时目录
mkdir -p /tmp/restore-test
# 从 Borg 恢复特定目录
cd /tmp/restore-test
borg extract /backup/borg-repo::latest etc/
# 对比关键配置文件
diff /tmp/restore-test/etc/ssh/sshd_config /etc/ssh/sshd_config
diff /tmp/restore-test/etc/firewalld/ /etc/firewalld/ -r
Terminal window
# 从 Borg 恢复应用目录
cd /tmp/restore-test
borg extract /backup/borg-repo::latest var/www/myapp/
# 验证文件数量和大小
find /tmp/restore-test/var/www/myapp -type f | wc -l
du -sh /tmp/restore-test/var/www/myapp

恢复后必须检查文件权限是否正确:

Terminal window
# 检查关键目录的权限
ls -la /tmp/restore-test/etc/ssh/
ls -la /tmp/restore-test/var/www/myapp/
# 检查 SELinux 上下文
ls -Z /tmp/restore-test/var/www/myapp/
Terminal window
rm -rf /tmp/restore-test

第 3 阶段:MySQL / MariaDB 恢复测试

Section titled “第 3 阶段:MySQL / MariaDB 恢复测试”
Terminal window
# 在测试服务器上安装 MySQL/MariaDB
dnf install -y mysql-server
systemctl start mysqld
# 创建专用测试数据库
mysql -u root -e "CREATE DATABASE restore_test;"
Terminal window
# 查看备份文件信息
ls -lh /backup/mysql/mydb-20260324.sql.gz
zcat /backup/mysql/mydb-20260324.sql.gz | head -30
# 恢复到测试数据库
zcat /backup/mysql/mydb-20260324.sql.gz | mysql -u root restore_test
Terminal window
# 检查表数量
mysql -u root -e "SELECT COUNT(*) AS table_count FROM information_schema.tables WHERE table_schema='restore_test';"
# 检查各表行数
mysql -u root -e "
SELECT table_name, table_rows
FROM information_schema.tables
WHERE table_schema='restore_test'
ORDER BY table_name;
"
# 抽样检查关键数据
mysql -u root -e "SELECT COUNT(*) FROM restore_test.users;"
mysql -u root -e "SELECT COUNT(*) FROM restore_test.orders;"
# 检查最新记录的时间戳(确认备份时间点)
mysql -u root -e "SELECT MAX(created_at) AS latest_record FROM restore_test.orders;"

3.4 恢复 XtraBackup 物理备份(如果使用)

Section titled “3.4 恢复 XtraBackup 物理备份(如果使用)”
Terminal window
# 准备备份
xtrabackup --prepare --target-dir=/backup/xtrabackup/20260324
# 恢复到测试目录
systemctl stop mysqld
xtrabackup --copy-back --target-dir=/backup/xtrabackup/20260324 --datadir=/var/lib/mysql-test
# 修正权限
chown -R mysql:mysql /var/lib/mysql-test
# 使用测试数据目录启动
mysqld --datadir=/var/lib/mysql-test --port=3307 &
# 验证
mysql -P 3307 -u root -e "SHOW DATABASES;"
Terminal window
mysql -u root -e "DROP DATABASE restore_test;"
Terminal window
# 在测试服务器上安装 PostgreSQL
dnf install -y postgresql-server
postgresql-setup --initdb
systemctl start postgresql
Terminal window
# 查看备份信息
ls -lh /backup/postgresql/mydb-20260324.sql.gz
zcat /backup/postgresql/mydb-20260324.sql.gz | head -30
# 创建测试数据库和用户
sudo -u postgres createdb restore_test
sudo -u postgres createuser --no-password testuser
# 恢复
zcat /backup/postgresql/mydb-20260324.sql.gz | sudo -u postgres psql restore_test
Terminal window
# 如果使用 pg_dump -Fc 格式
sudo -u postgres pg_restore -d restore_test /backup/postgresql/mydb-20260324.dump
# 仅恢复特定表
sudo -u postgres pg_restore -d restore_test -t users /backup/postgresql/mydb-20260324.dump
Terminal window
# 检查表数量
sudo -u postgres psql restore_test -c "
SELECT COUNT(*) AS table_count
FROM information_schema.tables
WHERE table_schema = 'public';
"
# 检查各表行数
sudo -u postgres psql restore_test -c "
SELECT schemaname, relname AS table_name, n_live_tup AS row_count
FROM pg_stat_user_tables
ORDER BY relname;
"
# 抽样检查
sudo -u postgres psql restore_test -c "SELECT COUNT(*) FROM users;"
sudo -u postgres psql restore_test -c "SELECT MAX(created_at) FROM orders;"
# 检查索引完整性
sudo -u postgres psql restore_test -c "REINDEX DATABASE restore_test;"

4.5 恢复基于 WAL 的时间点恢复(PITR)

Section titled “4.5 恢复基于 WAL 的时间点恢复(PITR)”
Terminal window
# 准备恢复目录
mkdir -p /var/lib/pgsql/restore-test
# 恢复基础备份
tar xzf /backup/postgresql/base-20260324.tar.gz -C /var/lib/pgsql/restore-test/
# 配置恢复参数
cat > /var/lib/pgsql/restore-test/postgresql.auto.conf << 'EOF'
restore_command = 'cp /backup/postgresql/wal/%f %p'
recovery_target_time = '2026-03-24 12:00:00'
recovery_target_action = 'promote'
EOF
# 创建恢复信号文件
touch /var/lib/pgsql/restore-test/recovery.signal
# 修正权限
chown -R postgres:postgres /var/lib/pgsql/restore-test/
# 以不同端口启动恢复实例
sudo -u postgres pg_ctl start -D /var/lib/pgsql/restore-test -o "-p 5433"
# 验证恢复时间点
psql -p 5433 -U postgres -c "SELECT pg_last_xact_replay_timestamp();"
Terminal window
sudo -u postgres dropdb restore_test
# 如果启动了 PITR 测试实例
sudo -u postgres pg_ctl stop -D /var/lib/pgsql/restore-test
rm -rf /var/lib/pgsql/restore-test

每次演练都必须详细记录结果。使用以下模板:

===== 备份恢复演练报告 =====
日期:2026-03-25
执行人:[姓名]
环境:测试服务器 restore-test-01
--- 备份完整性验证 ---
[PASS/FAIL] Borg 仓库校验
[PASS/FAIL] 数据块完整性
备注:
--- 文件恢复 ---
[PASS/FAIL] 系统配置文件恢复
[PASS/FAIL] 应用数据恢复
[PASS/FAIL] 文件权限正确
备注:
--- MySQL 恢复 ---
[PASS/FAIL] 备份文件加载成功
[PASS/FAIL] 表数量一致:生产 XX 表,恢复 XX 表
[PASS/FAIL] 行数一致
[PASS/FAIL] 最新数据时间戳符合预期
恢复耗时:XX 分钟
备注:
--- PostgreSQL 恢复 ---
[PASS/FAIL] 备份文件加载成功
[PASS/FAIL] 表数量一致:生产 XX 表,恢复 XX 表
[PASS/FAIL] 行数一致
[PASS/FAIL] 索引重建成功
恢复耗时:XX 分钟
备注:
--- 发现的问题 ---
1. [描述问题及影响]
2. [描述问题及影响]
--- 改进措施 ---
1. [具体改进行动及负责人]
2. [具体改进行动及负责人]
--- 下次演练计划 ---
日期:2026-06-25
范围:[全量/增量/特定系统]
季度月份演练内容预计耗时
Q13 月全量恢复演练(文件 + 数据库)4 小时
Q26 月数据库恢复专项 + PITR 测试2 小时
Q39 月全量恢复演练(含灾难恢复场景)4 小时
Q412 月文件恢复 + 备份策略年度评审3 小时
Terminal window
# 创建季度演练提醒的 cron 任务
cat > /etc/cron.d/backup-drill-reminder << 'EOF'
# 每季度第一个工作日发送提醒
0 9 1 3,6,9,12 * root echo "备份恢复演练提醒:请在本月内完成季度备份恢复演练。" | mail -s "[ACTION] 季度备份恢复演练" ops-team@example.com
EOF

除了人工演练,也应该自动化日常的备份校验:

cat > /usr/local/bin/backup-verify.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
LOG="/var/log/backup-verify.log"
ALERT_EMAIL="ops-team@example.com"
ERRORS=0
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG"
}
log "===== 开始备份验证 ====="
# 1. 检查 Borg 备份
if borg check /backup/borg-repo 2>> "$LOG"; then
log "[PASS] Borg 仓库完整性检查通过"
else
log "[FAIL] Borg 仓库完整性检查失败"
((ERRORS++))
fi
# 2. 检查最近备份的时间(不超过 25 小时)
LATEST=$(borg list --sort-by timestamp --last 1 --format '{time}' /backup/borg-repo)
LATEST_TS=$(date -d "$LATEST" +%s)
NOW_TS=$(date +%s)
AGE_HOURS=$(( (NOW_TS - LATEST_TS) / 3600 ))
if [ "$AGE_HOURS" -lt 25 ]; then
log "[PASS] 最近备份时间在 ${AGE_HOURS} 小时前"
else
log "[FAIL] 最近备份已过期:${AGE_HOURS} 小时前"
((ERRORS++))
fi
# 3. 检查备份大小(不应为零或异常小)
LATEST_SIZE=$(borg info /backup/borg-repo::latest --json | python3 -c "import sys,json; print(json.load(sys.stdin)['archives'][0]['stats']['original_size'])")
if [ "$LATEST_SIZE" -gt 1048576 ]; then
log "[PASS] 备份大小正常:$LATEST_SIZE bytes"
else
log "[FAIL] 备份大小异常:$LATEST_SIZE bytes"
((ERRORS++))
fi
# 报告结果
if [ "$ERRORS" -gt 0 ]; then
log "验证完成,发现 ${ERRORS} 个问题"
tail -20 "$LOG" | mail -s "[ALERT] 备份验证失败 (${ERRORS} errors)" "$ALERT_EMAIL"
else
log "验证完成,全部通过"
fi
SCRIPT
chmod +x /usr/local/bin/backup-verify.sh

设置每日自动验证:

Terminal window
cat > /etc/cron.d/backup-verify << 'EOF'
0 6 * * * root /usr/local/bin/backup-verify.sh
EOF
问题原因解决方案
恢复后数据库启动失败版本不匹配确保测试环境与生产使用相同的数据库版本
恢复后文件权限错误tar 解压未保留属性使用 tar -xpf 保留权限,恢复后运行 restorecon -Rv
Borg 报告仓库损坏备份过程中断或磁盘故障运行 borg check --repair;检查备份磁盘健康状态
恢复时间过长备份数据量大考虑增量恢复策略;使用更快的存储介质
WAL 文件缺失归档配置错误检查 archive_command 配置;确保归档目录空间充足

备份恢复演练不是一次性任务,而是持续的运维实践。关键要点:

  1. 验证第一 - 没有验证过的备份不可信
  2. 文档先行 - 每次演练都产出可执行的操作文档
  3. 定期执行 - 至少每季度一次完整演练
  4. 全员参与 - 不依赖单一人员
  5. 持续改进 - 每次演练后回顾并优化流程