备份、容灾与恢复演练

备份这件事只有一句话是真理:没恢复过的备份等于没有备份。很多团队每天跑 mysqldump、文件静静躺在磁盘上,真出事时才发现压缩包是 0 字节、路径写错、或者恢复出来的数据少了三天的订单。本章讲备份三要素、3-2-1 原则、具体命令,以及怎么用一次恢复演练把「备份」变成「可恢复」。

要解决的问题

  • 什么该备份、多久备一次、保留多久?
  • 数据库怎么备才既一致又不锁表?
  • 代码、配置、证书、上传目录分别怎么备?
  • 怎么证明备份真的能恢复?
  • 整机挂掉时,怎么在 30 分钟内重建服务?

备份三要素与 3-2-1

备份方案只要回答三个问题就完整了:备什么(对象)、多久备一次(频率)、留多久(保留策略)。再加一条业界通行的 3-2-1 原则:至少 3 份副本、存放在 2 种不同介质、其中 1 份在异地

备份对象频率保留策略存放位置
数据库全量每日一次(业务低谷)本地 7 天 + 异地 30 天对象存储(与服务器不同账号更佳)
数据库增量/binlog持续7 天异地
上传目录 / 用户文件每日增量30 天对象存储
配置与证书脚本每次变更全历史Git 仓库(敏感值走加密或环境变量)
整机状态每周 / 重大变更前2~3 个云平台快照

最常见的偷懒做法是「备份写到本机 /backup 目录」——机器磁盘坏了、被勒索加密了、机房故障了,备份和数据一起消失。备份必须离开这台机器,这是最低要求。

数据库备份

# 1) 一致性快照:--single-transaction 让 InnoDB 在事务里取一致快照,不锁表
mysqldump --single-transaction --quick --routines --triggers --events \
  --default-character-set=utf8mb4 \
  -u backup_user -p"$MYSQL_PWD" mydb | gzip -9 > /backup/mysql/mydb-$(date +%F).sql.gz

# 2) 立即做一次可读性校验(损坏的备份比没有备份更危险,因为它让你以为有备份)
zgrep -c 'INSERT INTO' /backup/mysql/mydb-$(date +%F).sql.gz

# 3) 异地存放:同步到对象存储或另一台机器
rsync -avz --delete /backup/mysql/ backup@backup-host:/data/mysql/
参数作用注意
--single-transactionInnoDB 一致性快照,不锁表只对 InnoDB 有效;MyISAM 表仍需 --lock-tables
--quick逐行读取而不是全量载入内存大表必备
--routines --triggers --events带上存储过程、触发器、事件默认不导出,漏了恢复后业务会静默出错
--default-character-set=utf8mb4明确字符集避免恢复后中文乱码
-p"$MYSQL_PWD"用环境变量传密码命令行明文会被 ps 看到

--single-transaction 不是万能保险:它保证的是 InnoDB 数据的一致性,如果同时有 DDL 在执行,快照可能失败或不准,所以备份时间要避开结构变更窗口。

文件与配置备份

# 上传目录:增量同步,--delete 让备份与源保持一致(源端被误删的文件在备份里也会删)
rsync -avz --delete /srv/www/uploads/ backup@backup-host:/data/uploads/

# 配置目录:同样同步一份,同时确保配置已进 Git
rsync -avz --delete /etc/nginx/ backup@backup-host:/data/etc-nginx/

# 配置仓库化:改动可追溯、可回滚、可重建
cd /srv/ops && git add -A && git commit -m "nginx: $(date +%F)" && git push

配置备份的关键不是「复制文件」,而是让配置可重建:把 Nginx 配置、systemd unit、部署脚本、证书申请命令都放进一个私有仓库,新机器上 git clone 就能恢复八成环境。注意仓库里绝不能提交私钥与数据库密码,用 .gitignore 加环境变量管理。

增量加密备份:restic / borg

全量 mysqldump + rsync 够用但重;增量、加密、去重的工具能让备份更快、更安全。两者思路接近,选一个即可。

# restic:仓库可以直接放在对象存储上,适合云环境
export RESTIC_REPOSITORY=s3:https://oss-cn-hangzhou.aliyuncs.com/my-backup
export RESTIC_PASSWORD_FILE=/root/.restic-pass        # 仓库密码文件权限 600
restic init
restic backup /srv/www /etc/nginx --exclude-file=/etc/restic-exclude.txt
restic snapshots --last
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune   # 保留策略 + 清理
restic check --read-data-subset=5%                    # 抽样校验仓库完整性
restic restore latest --target /tmp/restore-test --include /srv/www/uploads
# borg:本地或挂载盘上做去重加密仓库
borg init --encryption=repokey /backup/borg-repo
borg create --stats --compression zstd /backup/borg-repo::'{now:%F}' /srv/www /etc/nginx
borg prune --keep-daily 7 --keep-weekly 4 --keep-monthly 12 /backup/borg-repo

两者都比 tar 多出的三个能力值得为它换工具:增量(只存变化块)、加密(备份泄露不等于数据泄露)、校验(restic check 能确认仓库没坏)。仓库密码文件丢了等于备份全废,所以密码要单独存在密码管理器或另一台机器上。

备份校验:能恢复才算备份

# 在测试库上恢复一份,与源库比对行数——这是唯一可信的校验方式
gunzip -c /backup/mysql/mydb-2025-01-08.sql.gz | mysql -u root -p restore_test
mysql -N -e "SELECT COUNT(*) FROM restore_test.orders"  > /tmp/rows_backup.txt
mysql -N -e "SELECT COUNT(*) FROM mydb.orders"          > /tmp/rows_live.txt
diff /tmp/rows_backup.txt /tmp/rows_live.txt && echo '备份校验通过'

校验频率建议:每月一次完整恢复演练,每周一次抽样校验。演练要落到具体动作上——恢复到测试库、跑一遍核心查询、确认行数与关键字段正确,并记录耗时(这个耗时就是灾难恢复的真实耗时基线)。

定时任务与失败告警

# /etc/cron.d/backup —— 定时执行并落日志
0 3 * * * root /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
#!/usr/bin/env bash
# /opt/scripts/backup.sh —— 关键点:任何一步失败都要告警,不能静默
set -euo pipefail
WEBHOOK_URL="https://your-webhook"
trap 'curl -s -X POST -H "Content-Type: application/json" \
  -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"备份失败:第 $LINENO 行\"}}" "$WEBHOOK_URL"' ERR
mysqldump --single-transaction --routines -u backup_user -p"$MYSQL_PWD" mydb \
  | gzip -9 > /backup/mysql/mydb-$(date +%F).sql.gz
test -s /backup/mysql/mydb-$(date +%F).sql.gz        # 文件不能是 0 字节
rsync -avz /backup/mysql/ backup@backup-host:/data/mysql/
find /backup/mysql -name '*.sql.gz' -mtime +7 -delete

set -euo pipefail 加上 trap ... ERR 是这份脚本的核心:备份脚本最危险的状态是「失败了但没人知道」

灾难恢复:整机挂掉的 30 分钟清单

步骤具体动作目标耗时
1 拉起机器云控制台按同配置新建,或从快照创建5 分钟
2 恢复数据从对象存储下载最近备份并导入10 分钟
3 部署代码拉取上一个可用 tag,用 CI 产物或重新构建5 分钟
4 恢复配置从 Git 拉配置、放证书(或重新签发)、启服务3 分钟
5 切换解析把域名 A 记录指向新 IP(TTL 要提前调小到 60s2 分钟
6 验证拨测、登录、核心流程走一遍5 分钟

要让这 30 分钟成立,有三件事必须提前做:DNS 的 TTL 平时就保持在 300 秒以内、备份恢复的耗时被演练验证过、配置与部署脚本能在新机器上一条命令重建。真正意义上的「容灾」还需要多可用区或异地机房,那是成本更高的下一阶段;对多数项目来说,「备份能恢复 + 30 分钟能重建」就已经覆盖了 95% 的灾难场景

常见坑

现象做法
备份只存本机机器故障或勒索加密时数据一起丢异地存放,3-2-1 原则
从没恢复过出事才发现备份是空的每月恢复演练,每周抽样校验
备份脚本静默失败连续几周没有新备份,没人知道set -euo pipefail + trap + 告警
漏了存储过程与触发器恢复后业务逻辑静默失效--routines --triggers --events
命令行明文写密码密码出现在 ps 输出与历史记录里用环境变量或 --defaults-extra-file
只备份数据不备份配置机器重建后要凭记忆重配 Nginx配置进 Git,脚本化重建

小结:备份方案要能被一句话说清「备什么、多久一次、留多久、放在哪」,并满足 3-2-1 原则;数据库用 mysqldump --single-transaction --routines 加压缩和异地同步,文件与配置用 rsync 加 Git 仓库化;想要增量与加密就上 resticborg;最关键的一步是定期在测试库恢复一次并比对行数,同时给备份脚本加失败告警;最后把「整机挂掉怎么在 30 分钟内重建」写成清单并演练一遍——备份的价值不在于存了多少份,而在于恢复用了几分钟。

笔记加载中…