跳转至

案例:定时备份配置与恢复演练

场景背景

  • 时间:2026-06-18
  • 角色:运维工程师,负责 jump 数据库的备份策略
  • 现状:没有自动备份,只靠偶尔手动 mysqldump
  • 目标:建立每日自动备份 + 每周恢复演练,确保 RPO < 1 天,RTO < 2 小时
  • 数据库:MySQL 8.0.32,业务库 jump,数据量 23.4 GB

生活化比喻:数据库备份就像给重要文档做复印件。每天复印一次(每日备份),复印件放在保险箱(异地存储)。如果原件丢了(数据库损坏),你可以用复印件恢复,最多损失一天的工作(RPO = 1 天)。恢复演练就是定期检查"复印件还能不能看清"——别等到原件丢了才发现复印件是糊的。


备份策略设计(5 分钟)

需求分析

指标 目标 说明
RPO < 1 天 最多丢失 1 天数据
RTO < 2 小时 从故障到恢复业务 < 2 小时
保留周期 30 天 保留最近 30 天备份
存储位置 本地 + 异地 本地快速恢复,异地容灾
备份类型 全量 + 增量 每日全量(数据量小),或每周全量 + 每日增量

方案选择

方案 备份速度 恢复速度 空间占用 适用场景
mysqldump(逻辑) 慢(23 GB 约 30 分钟) 慢(恢复 1-2 小时) 小(压缩后 3-5 GB) 数据量 < 50 GB,跨版本恢复
xtrabackup(物理) 快(23 GB 约 5 分钟) 快(恢复 10-20 分钟) 大(23 GB) 数据量 > 50 GB,需要快速恢复
云数据库自动备份 自动 自动 按云厂商 云数据库,无运维负担

本案例数据量 23.4 GB,选择 mysqldump + 压缩,简单可靠,恢复时间 1-2 小时满足 RTO。


步骤 1:手动备份测试(5 分钟)

使用 dbskiter scheduler 备份

# 全量备份
dbskiter --database=jump scheduler backup --type=full

# 查看备份文件
ls -lh /backup/

预期输出

备份完成:
  文件: /backup/jump_full_2026-06-18_03-00-00.sql.gz
  大小: 4.2 GB (压缩后)
  耗时: 28 分 15 秒
  校验: SHA256 = a3f5c8...

手动 mysqldump(了解底层)

# 创建备份目录
mkdir -p /backup/mysql

# 全量备份(带压缩)
mysqldump -u root -p \
  --single-transaction \
  --routines \
  --triggers \
  --databases jump \
  | gzip > /backup/mysql/jump_full_$(date +%Y%m%d_%H%M%S).sql.gz

# 参数说明:
# --single-transaction: 保证一致性,不锁表(InnoDB)
# --routines: 备份存储过程
# --triggers: 备份触发器
# --databases: 指定数据库
# | gzip: 管道压缩,减少磁盘占用

步骤 2:配置定时备份(5 分钟)

方案 A:使用 dbskiter scheduler(推荐)

# 添加每日凌晨 3 点全量备份任务
dbskiter --database=jump scheduler task add daily_backup "0 3 * * *" \
  --command="backup --type=full --compress --output=/backup/mysql/"

# 添加每周日凌晨 4 点清理旧备份任务
dbskiter --database=jump scheduler task add cleanup_backup "0 4 * * 0" \
  --command="backup-cleanup --keep=30 --dir=/backup/mysql/"

# 查看任务列表
dbskiter --database=jump scheduler task list

预期输出

任务列表:
  ID    名称              调度             状态    上次执行         下次执行
  ----  ----------------  ---------------  ------  ---------------  ---------------
  1     daily_backup      0 3 * * *        启用    2026-06-18 03:00 2026-06-19 03:00
  2     cleanup_backup    0 4 * * 0        启用    2026-06-15 04:00 2026-06-22 04:00

方案 B:使用 crontab(传统方式)

# 编辑 crontab
crontab -e

# 添加以下内容
# 每天凌晨 3 点全量备份
0 3 * * * /usr/local/bin/backup_mysql.sh >> /var/log/backup_mysql.log 2>&1

# 每周日凌晨 4 点清理 30 天前的备份
0 4 * * 0 find /backup/mysql -name "*.sql.gz" -mtime +30 -delete >> /var/log/backup_cleanup.log 2>&1

备份脚本 /usr/local/bin/backup_mysql.sh

#!/bin/bash
# 文件名:backup_mysql.sh
# 功能:MySQL 全量备份 + 压缩 + 校验

BACKUP_DIR="/backup/mysql"
DB_NAME="jump"
DB_USER="backup_user"
DB_PASS="backup_password"
DATE_STR=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_full_${DATE_STR}.sql.gz"

# 创建备份目录
mkdir -p ${BACKUP_DIR}

# 执行备份
mysqldump -u ${DB_USER} -p${DB_PASS} \
  --single-transaction \
  --routines \
  --triggers \
  --databases ${DB_NAME} \
  | gzip > ${BACKUP_FILE}

# 检查备份是否成功
if [ $? -eq 0 ]; then
    # 计算校验和
    sha256sum ${BACKUP_FILE} > ${BACKUP_FILE}.sha256

    # 记录日志
    echo "[$(date)] 备份成功: ${BACKUP_FILE}, 大小: $(du -h ${BACKUP_FILE} | cut -f1)"

    # 可选:上传到异地存储(S3/阿里云OSS)
    # aws s3 cp ${BACKUP_FILE} s3://my-backup-bucket/mysql/
else
    echo "[$(date)] 备份失败!" >&2
    # 发送告警(钉钉/企业微信/邮件)
    curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=xxx" \
         -H "Content-Type: application/json" \
         -d "{\"msgtype\": \"text\", \"text\": {\"content\": \"[告警] MySQL 备份失败: ${DB_NAME}\"}}"
    exit 1
fi

设置权限:

chmod +x /usr/local/bin/backup_mysql.sh
chown root:root /usr/local/bin/backup_mysql.sh
chmod 600 /usr/local/bin/backup_mysql.sh  # 脚本包含密码,严格权限

步骤 3:备份验证(10 分钟)

检查备份完整性

# 验证校验和
cd /backup/mysql
sha256sum -c jump_full_2026-06-18_03-00-00.sql.gz.sha256

# 预期输出
jump_full_2026-06-18_03-00-00.sql.gz: OK

抽查备份内容

# 不解压,查看备份文件头部(确认包含建表语句)
gunzip -c jump_full_2026-06-18_03-00-00.sql.gz | head -n 30

# 预期看到
-- MySQL dump 10.13  Distrib 8.0.32
-- Host: localhost    Database: jump
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `jump` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `jump`;
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` int NOT NULL AUTO_INCREMENT,
  ...

恢复演练(核心!)

# 1. 创建测试数据库(不要直接恢复生产库!)
mysql -u root -p -e "CREATE DATABASE jump_restore_test;"

# 2. 恢复备份到测试库
zcat /backup/mysql/jump_full_2026-06-18_03-00-00.sql.gz | mysql -u root -p jump_restore_test

# 3. 验证数据完整性
mysql -u root -p -e "
USE jump_restore_test;
SELECT 
    (SELECT COUNT(*) FROM users) as users_count,
    (SELECT COUNT(*) FROM orders) as orders_count,
    (SELECT COUNT(*) FROM logs) as logs_count;
"

# 4. 对比生产库数据量
mysql -u root -p -e "
USE jump;
SELECT 
    (SELECT COUNT(*) FROM users) as users_count,
    (SELECT COUNT(*) FROM orders) as orders_count,
    (SELECT COUNT(*) FROM logs) as logs_count;
"

# 5. 确认一致后删除测试库
mysql -u root -p -e "DROP DATABASE jump_restore_test;"

验证标准

检查项 方法 通过标准
备份文件存在 ls -lh 文件存在且大小 > 0
校验和正确 sha256sum -c 显示 OK
可解压 gunzip -t 无报错
包含建表语句 head -n 30 包含 CREATE TABLE
数据量一致 SELECT COUNT(*) 与生产库一致
恢复成功 恢复到测试库 无报错,数据可查询

恢复演练必须定期做! 很多团队备份做了,但恢复时才发现备份文件损坏、数据不完整。


步骤 4:使用 dbskiter 验证备份(1 分钟)

# 验证备份文件完整性
dbskiter --database=jump scheduler backup-verify /backup/mysql/jump_full_2026-06-18_03-00-00.sql.gz

# 预期输出
备份验证结果:
  文件: jump_full_2026-06-18_03-00-00.sql.gz
  大小: 4.2 GB
  压缩: gzip, 正常
  校验和: 匹配
  内容检查: 包含 507 张表, 数据行数 21,456,000
  状态: 通过

步骤 5:恢复演练计划(自动化)

每周恢复演练脚本

#!/bin/bash
# /opt/scripts/weekly_restore_test.sh
# 功能:每周自动恢复最新备份到测试库,验证数据完整性

BACKUP_DIR="/backup/mysql"
DB_NAME="jump"
TEST_DB="${DB_NAME}_restore_test"

# 找到最新备份
LATEST_BACKUP=$(ls -t ${BACKUP_DIR}/${DB_NAME}_full_*.sql.gz | head -n 1)

echo "[$(date)] 开始恢复演练: ${LATEST_BACKUP}"

# 1. 清理旧测试库
mysql -u root -p -e "DROP DATABASE IF EXISTS ${TEST_DB}; CREATE DATABASE ${TEST_DB};" 2>/dev/null

# 2. 恢复备份
zcat ${LATEST_BACKUP} | mysql -u root -p ${TEST_DB}

if [ $? -ne 0 ]; then
    echo "[$(date)] 恢复失败!" >&2
    # 发送告警
    curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=xxx" \
         -H "Content-Type: application/json" \
         -d "{\"msgtype\": \"text\", \"text\": {\"content\": \"[告警] 备份恢复演练失败: ${LATEST_BACKUP}\"}}"
    exit 1
fi

# 3. 验证数据量
PROD_COUNT=$(mysql -u root -p -N -e "SELECT COUNT(*) FROM ${DB_NAME}.users" 2>/dev/null)
TEST_COUNT=$(mysql -u root -p -N -e "SELECT COUNT(*) FROM ${TEST_DB}.users" 2>/dev/null)

if [ "$PROD_COUNT" -eq "$TEST_COUNT" ]; then
    echo "[$(date)] 恢复演练成功! 用户表数据一致: ${PROD_COUNT} 行"
    # 可选:发送成功通知
else
    echo "[$(date)] 恢复演练失败! 数据不一致: 生产=${PROD_COUNT}, 测试=${TEST_COUNT}" >&2
    # 发送告警
    exit 1
fi

# 4. 清理测试库
mysql -u root -p -e "DROP DATABASE ${TEST_DB};" 2>/dev/null

echo "[$(date)] 恢复演练完成"

加入 crontab:

# 每周日凌晨 5 点执行恢复演练
0 5 * * 0 /opt/scripts/weekly_restore_test.sh >> /var/log/restore_test.log 2>&1

步骤 6:异地备份(高级)

上传到云存储

#!/bin/bash
# /opt/scripts/upload_backup_to_s3.sh

BACKUP_DIR="/backup/mysql"
S3_BUCKET="s3://my-company-backup/mysql/"

# 找到今天的新备份
TODAY_BACKUP=$(find ${BACKUP_DIR} -name "*.sql.gz" -mtime -1 | head -n 1)

if [ -n "$TODAY_BACKUP" ]; then
    aws s3 cp ${TODAY_BACKUP} ${S3_BUCKET}
    aws s3 cp ${TODAY_BACKUP}.sha256 ${S3_BUCKET}

    # 验证上传成功
    aws s3 ls ${S3_BUCKET}$(basename ${TODAY_BACKUP})

    if [ $? -eq 0 ]; then
        echo "[$(date)] 异地备份上传成功: ${TODAY_BACKUP}"
    else
        echo "[$(date)] 异地备份上传失败!" >&2
        exit 1
    fi
fi

备份策略检查清单

检查项 频率 负责人 状态
备份任务运行正常 每日 自动化 检查日志
备份文件大小合理 每日 自动化 异常波动告警
校验和验证 每日 自动化 sha256sum -c
恢复到测试库 每周 自动化 对比数据量
异地备份上传 每日 自动化 检查 S3/OSS
备份保留策略执行 每周 自动化 清理 30 天前备份
恢复演练报告 每周 运维 发给团队确认
备份方案 review 每季度 运维 + 开发 评估 RPO/RTO 是否满足

复盘要点

1. 备份核心原则

3-2-1 备份原则:
  3 份备份(原始 + 2 份副本)
  2 种介质(本地磁盘 + 云存储)
  1 份异地(不同地理位置)

2. 备份不等于恢复

阶段 常见问题 解决方案
备份 备份失败未告警 备份脚本加失败通知
备份 备份文件损坏 校验和验证
恢复 忘记恢复步骤 文档化恢复 SOP
恢复 恢复时间超预期 定期演练,记录实际 RTO
恢复 恢复后数据不完整 演练时对比数据量

3. 常见误区

误区 正确做法
"备份了就不用管" 必须定期恢复演练,验证备份可用
"备份文件越大越好" 压缩后合理大小,突然变大可能是备份了不该备份的表
"只备份数据文件" 必须备份表结构、存储过程、触发器、用户权限
"一个备份就够了" 至少本地 + 异地两份,防止单点故障
"恢复演练太麻烦" 自动化脚本每周跑,零成本验证
"mysqldump 太慢用 xtrabackup" 数据量 < 50 GB 时 mysqldump 更简单,压缩后空间小

延伸阅读