案例:定时备份配置与恢复演练¶
场景背景¶
- 时间: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 更简单,压缩后空间小 |