数据备份与恢复:策略、验证与演练
本文目录
备份这件事有个残酷的特性:它平时完全无法证明自己有效,出事时却必须立刻有效。多少团队的备份体系是"每晚一个 cron 脚本 + 从来没恢复过",直到误删一张表的那天才发现压缩包损坏、或者是六个月前的。这篇文章把备份当作一套带验收标准的工程来讲:先定 RPO/RTO,选对类型,保证一致性,然后是本文最重要的部分——验证与演练。
先定 RPO 和 RTO:所有方案都由这两个数字倒推
备份不是"越勤越好",频率和方案由两个业务指标决定:
| 指标 | 含义 | 问题形态 | 决定了什么 |
|---|---|---|---|
| RPO(恢复点目标) | 最多能接受丢多长时间的数据 | "凌晨备份,下午 3 点出事,丢 15 小时行吗?" | 备份频率与方式 |
| RTO(恢复时间目标) | 最多能接受多久不可用 | "恢复要 4 小时,业务能停 30 分钟吗?" | 恢复方案与资源预置 |
典型分级示例:
| 数据类别 | RPO | RTO | 对应方案 |
|---|---|---|---|
| 交易/订单库 | 分钟级 | < 30 分钟 | 连续归档(PITR)+ 热备 |
| 用户内容(文章/评论) | ≤ 1 小时 | 1–2 小时 | 每日全量 + 日志备份 |
| 配置与代码 | 0(在 Git) | 分钟级 | 版本库即备份 |
| 日志 | 1–2 天可接受 | 天级 | 分层保留,可重建 |
RPO 定下来,方案空间就锁死了:RPO 分钟级排除了每日全量逻辑导出(每天一次备份最坏丢一整天),必须上 WAL/binlog 连续归档。反过来,测试环境 RPO 一天就够,做实时复制是纯浪费。先跟业务方确认数字,再选技术,顺序不能反。
备份类型:全量、增量、差异
| 类型 | 备份内容 | 空间占用 | 恢复速度 | 恢复依赖 |
|---|---|---|---|---|
| 全量 | 全部数据 | 每次都最大 | 最快,单份即可恢复 | 无 |
| 增量 | 距上次任意备份的变化 | 每次最小 | 最慢,链式依赖 | 全量 + 之后每一份增量,断一环全废 |
| 差异 | 距上次全量的变化 | 越攒越大 | 较快 | 全量 + 最近一份差异 |
三者的关系用一个周计划看:周日全量,周一到周六若做增量,周六恢复要"周日全量 + 周一到周六 6 份";若做差异,周六恢复只要"周日全量 + 周六一份差异",但周六那份差异包含了整周的变化,会越滚越大。
生产实践的主流是**"每周全量 + 每日差异 + 数据库日志连续归档"**:差异的恢复链短(最多两份),日志归档把 RPO 从"天"压到"分钟"。纯增量方案只在备份窗口或传输带宽极其受限时考虑,且必须有严格的完整性校验,因为它把鸡蛋分散到了链条的每一环。
数据库备份:物理与逻辑是两条路线
| 维度 | 逻辑备份 | 物理备份 |
|---|---|---|
| 形态 | SQL 文本或导出格式(如 pg_dump 的 custom) | 数据文件 + 日志的原样拷贝 |
| 工具(PG) | pg_dump / pg_dumpall | pg_basebackup + WAL 归档 |
| 工具(MySQL 思路) | mysqldump、mydumper | 物理拷贝(需锁表协调)、Percona XtraBackup 类工具 |
| 跨版本/跨平台 | 好,天然向上兼容 | 差,绑定版本和平台 |
| 恢复速度 | 慢(重新执行 SQL) | 快(拷回文件即可) |
| 体量上限 | 中小库(GB 到几十 GB) | 大库(TB 级)首选 |
| PITR 能力 | 无 | 配合日志归档可实现 |
PostgreSQL 侧的两条命令各司其职:
# 逻辑备份:单库、格式可选、可单表恢复
pg_dump -Fc -d blog -f blog_$(date +%F).dump
# 物理备份:全库数据文件的整备,为 PITR 打基础
pg_basebackup -D /backup/base/$(date +%F) -X stream -P -h db.internal -U backup_user
MySQL 侧思路对称:mysqldump 加 --single-transaction(InnoDB 一致性快照,不锁库),物理路线用 XtraBackup 类工具做在线热备。不要用 cp 直接拷正在写的数据库目录——除非先停库或先做文件系统快照,正在写入的文件拷出来大概率是损坏的。
选型经验值:几十 GB 以下、恢复频率低、需要跨大版本迁移,逻辑备份够用;数据量大、要求分钟级 RTO、要做 PITR,物理备份 + 日志归档是唯一现实解。两者不互斥,**每周逻辑全量(防版本升级路径)+ 每日物理增量 + 连续归档(PITR)**是常见组合。
一致性:备份必须是某个时刻的完整切面
一份"备份了一半"的数据比没有更危险——你会以为有。一致性有三种保证手段:
手段一:数据库级一致导出。pg_dump 默认在单次一致性快照中读取全部数据,导出内容等价于"导出开始时刻的库状态";mysqldump 对 InnoDB 要显式加 --single-transaction,否则逐表导出中间的写入会让数据自相矛盾:
mysqldump --single-transaction --quick --routines --triggers \
--set-gtid-purged=OFF blog > blog_$(date +%F).sql
手段二:物理备份绑定日志点。pg_basebackup 记录了备份期间的 WAL 位置,恢复时先拷数据文件再重放日志到这个点,保证文件与日志对齐。
手段三:文件系统快照(LVM、云盘快照)。快照瞬时冻结文件系统状态,对数据库"看起来"是停机了一瞬间。两个前提:数据库要在快照前做检查点/刷缓冲(PostgreSQL 可在快照前 CHECKPOINT),且快照留在同一块盘上不算备份——它防的是误操作,不防盘损毁。快照必须再复制出去才算数。
备份窗口:别让备份压垮生产
备份是在生产机器上跑的重负载任务,"白天备份把 CPU 打满导致线上慢查询"和"备份任务本身失败"都真实发生过。常见的冲突与对策:
| 冲突 | 现象 | 对策 |
|---|---|---|
| 备份 vs 业务高峰 | 备份期间接口延迟整体抬升 | 备份窗口放业务低峰(凌晨),并限制备份进程资源 |
| 备份 vs IO | 磁盘被打满,日志写入排队 | 物理备份限速(如 pg_basebackup 的 --max-rate),逻辑备份分表错峰 |
| 备份 vs 备份 | 多个实例同时全量,网络与存储互相拖累 | 备份时间表全局错开,滚动执行 |
| 备份超时未完成 | 窗口结束业务恢复,但备份没做完 | 窗口超时任务要中止而不是"算了让它跑完",未完成的不入库 |
# pg_basebackup 限制物理备份的读取速率,保护生产 IO(单位 KiB/s,示例 32MiB/s)
pg_basebackup -D /backup/base/$(date +%F) -X stream --max-rate=32768 \
-h db.internal -U backup_user
经验值:备份任务对生产的影响要进入日常监控曲线——备份时段的延迟曲线如果没有可见抬升,说明资源规划是合理的;如果抬升明显,先限速而不是先换机器。备份窗口的结束时间要写进 runbook,因为恢复演练的耗时评估(RTO 预演)要在与窗口同规格的环境里做才有参考价值。
备份验证:不做验证的备份不是备份
验证分三层,每层的成本递增,覆盖的故障面也递增:
| 层级 | 验证内容 | 频率 | 成本 |
|---|---|---|---|
| 完整性 | 备份文件未损坏、未加密勒改 | 每次备份后自动 | 秒级 |
| 可读性 | 目录/归档能被正确解析 | 每次备份后自动 | 分钟级 |
| 可恢复性 | 能真正恢复出可用数据 | 每周/每月 | 小时级,需要隔离环境 |
第一二层用工具自动完成:
# pg_dump 自定义格式自带目录,--list 能列出内容说明文件结构完好
pg_restore --list blog_2026-10-22.dump | head -20
# 归档级校验:备份脚本末尾追加,失败立即报警而不是等出事
sha256sum blog_2026-10-22.dump > blog_2026-10-22.dump.sha256
sha256sum -c blog_2026-10-22.dump.sha256 && echo "backup verified" \
|| alert "backup checksum FAILED: blog_2026-10-22"
第三层是关键:定期把备份恢复到一个隔离环境,验证到"业务可以跑"的深度:
# 在隔离的恢复演练机上:从备份文件拉起一个完整实例
createdb restore_test_$(date +%F)
pg_restore -d restore_test_2026-10-22 -j 4 blog_2026-10-22.dump
# 验收不只看"命令成功",要验证数据本身
psql -d restore_test_2026-10-22 -c "SELECT count(*) FROM articles;" # 行数与生产对账
psql -d restore_test_2026-10-22 -c "SELECT max(created_at) FROM articles;" # 时间点符合备份时间
"能恢复"的验收标准要写具体:恢复出的实例能接受应用连接、核心表行数与生产对账记录一致、关键查询延迟正常。验证脚本与备份脚本同样重要,应该一起进版本库、一起被监控。把这一步忘了的备份体系,本质上是一堆正在变旧的压缩包。
异地备份:单机备份不算灾备
所有备份都放在被备份的那台机器(或同一机架、同一可用区)上,等于没有灾备。要防的事件清单很具体:机器/机房损毁(火、水、电)、误操作 rm -rf 波及挂载盘、勒索软件加密所有可达存储。第三种最容易被忽略——勒索样本会优先加密同网段的 NFS/SMB 挂载,"备份也挂在这台服务器上"的方案在攻击面前是同一个文件系统。
行业共识是 3-2-1 原则:3 份数据副本,2 种不同介质,其中 1 份异地。异地副本的实现与纪律:
# 异地传输必须加密:ssh 通道传输备份(主机名与凭证由部署系统注入,示例脱敏)
scp -p blog_2026-10-22.dump backup-remote.internal:/srv/backups/blog/
# 或用 rsync 走 ssh,支持断点续传并校验
rsync -az --partial blog_2026-10-22.dump backup-remote.internal:/srv/backups/blog/
异地介质的档次从低到高:另一机房的对象存储(版本化开启,防覆盖误删)、离线的加密硬盘轮换、云上 immutable(对象锁)存储防勒索加密。无论哪档,备份存储的凭证必须与生产隔离——生产服务器被攻破时不该能删掉异地备份,这是勒索场景下活下来的底线。备份文件本身也应静态加密(磁盘加密或备份工具加密选项),密钥单独管理。
文件类备份:rsync 与硬链接增量
代码进 Git,剩下的"文件类数据"(上传的图片、导出的报表)用 rsync 系方案。先说 rsync 最容易被忽略的参数:
# --checksum:用校验和而非"大小+时间戳"判断差异,速度慢但结果可靠
# 时间戳在异常断电或复制后可能说谎,重要数据用校验和
rsync -av --checksum --delete /srv/uploads/ backup-remote.internal:/srv/backups/uploads/
# --delete 让目标与源同步删除,恢复误删文件前先停掉同步任务,否则删操作会同步过去
rsync --delete 的双向陷阱要写进 runbook:它防的是"目标越积越多",付出的是"源被误删的内容也会从备份里消失"。搭配每日快照式目录才安全,这就是硬链接增量方案(time-machine 风格)的思路:
# 每天一个"看起来完整"的快照目录,未变化的文件用硬链接共享存储,只占一份空间
rsync -a --delete --link-dest=/srv/backups/uploads/2026-10-21 \
/srv/uploads/ /srv/backups/uploads/2026-10-22/
效果:/srv/backups/uploads/2026-10-22 是当日完整状态,但只有变化过的文件占新空间——每天几十 MB 的变化量,快照目录却是完整的几百 GB 视图,恢复任意一天的误删只要 cp 那个文件出来。类似方案也直接有现成工具(restic、borg 一类),自建的价值是理解机制,生产长期用还是选带校验、压缩和加密的成熟工具。
轮换保留:祖父-父-子(GFS)
无差别保留所有备份既贵又没用,GFS(Grandfather-Father-Son)用"三代"结构平衡覆盖面和成本:
| 代际 | 备份 | 保留 | 覆盖的恢复需求 |
|---|---|---|---|
| 子(日备) | 每日 | 7–14 份 | 最近几天的误操作(最常见) |
| 父(周备) | 每周日 | 4–8 份 | 上周/上上周发现的问题 |
| 祖父(月备) | 每月 1 号 | 6–12 份 | 季度级回溯、合规要求 |
要点:月备是最后一道闸,保留期最长、要异地;删旧备份必须脚本化(按日期目录清理),手工删迟早出事故;保留策略要和业务对齐——"发票数据要保留 N 年"是合规要求,超出 GFS 的部分归档到对象存储的生命周期规则去管,不要混在备份里。
# 轮换脚本按日期目录清理,只按规则删、按白名单保护,杜绝误删
find /srv/backups -maxdepth 2 -type d -name '20*' -mtime +14 -regex '.*/uploads/.*' \
-exec rm -rf {} +
监控备份任务本身:沉默的失败最危险
备份失败的模式有个共同点:不报错、不发声。cron 被注释了、磁盘满了脚本静默退出、凭证过期鉴权失败但没人看 stderr、备份文件一直在但大小是零——每种都能潜伏几个月,直到恢复那天。所以备份体系要被当成一个"被监控的服务"对待,监控点包括:
- 存在性监控:备份目录里今天有没有新文件。最简单也最有效,一个每小时的检查脚本加告警即可。
- 新鲜度与大小:最新备份的 mtime 距今多久、文件大小是否在历史区间内(突然从 2GB 变 3KB 是实锤故障)。
- 任务退出状态:备份脚本的 stdout/stderr 落到独立日志,退出码非零要触发告警而不是静默重试。
- 归档连续性:PITR 依赖的 WAL/binlog 归档,监控最大延迟(上次归档成功的时间点距今多久)。
# 存在性 + 大小区间的检查思路:超过 26 小时没有新全量即告警
latest=$(ls -t /srv/backups/blog/*.dump | head -1)
age=$(( $(date +%s) - $(stat -c %Y "$latest") ))
[ "$age" -gt 93600 ] && alert "blog backup stale: ${age}s since $latest"
这套检查本身也要纳入演练:故意停掉备份 cron 一天,确认告警真的响了。监控备份的告警链路和业务告警链路要分开——数据库挂掉的时候,"备份失败"告警淹没在故障告警里没有意义,但恢复之后必须有复盘确认备份已恢复。
恢复演练:把恢复变成肌肉记忆
备份体系最后一次验收是"删库之后多久能回来",这个能力只能靠演练建立。一份可执行的恢复清单:
- 确认故障范围:哪张表/哪个库/哪个时间点?决定恢复粒度——PITR 恢复到精确时刻 vs 逻辑导入一张表。
- 冻结写入:停止应用写入(或切到维护页),防止恢复期间产生新数据又丢一次。
- 预留恢复空间:物理恢复需要 2 倍以上数据目录空间,演练时就要确认这个容量真实存在。
- 执行恢复:按 runbook 跑,不在事故现场即兴发挥命令参数。
- 对账验收:核心表行数、最近业务时间戳、应用冒烟测试(注册、下单各走一遍)。
- 复盘:RTO 实际耗时 vs 目标、哪一步卡壳、runbook 更新。
psql 侧做 PITR 级别恢复时用到的时间点定位命令:
-- 恢复前先确认要恢复到的准确时刻:从应用日志找最后一致操作的时间戳
SELECT pg_walfile_name(pg_current_wal_lsn()); -- 记录当前 WAL 位置,决定重放终点
演练频率的建议:每月一次表级恢复演练(30 分钟内),每季度一次全量 PITR 演练(含应用验收)。演练结果记录两个硬指标:实际 RTO 耗时、发现的故障数。故障演练(game day)更进一步——由第三人在演练环境故意制造数据事故,值班同学按 runbook 恢复。演练发现的每一个问题,价值都高于十次成功的备份。
自动化的最小形态是把"每周取一份备份到隔离环境恢复"做成脚本,机器做体力活、人做验收:
#!/usr/bin/env bash
# weekly_restore_drill.sh:在演练机上自动恢复最新周备份并输出对账数据
set -euo pipefail
BACKUP=$(ls -t /srv/backups/blog/blog_*.dump | head -1)
DB="drill_$(date +%F)"
createdb "$DB"
start=$(date +%s)
pg_restore -d "$DB" -j 4 "$BACKUP"
echo "restore_seconds=$(( $(date +%s) - start ))" # RTO 耗时自动记录
psql -d "$DB" -Atc "SELECT 'articles', count(*) FROM articles
UNION ALL SELECT 'users', count(*) FROM users"
dropdb "$DB" # 演练完立即回收,避免演练数据被误当成生产
注意事项与边界
- 备份脚本里的数据库凭证是最敏感的一类密钥:它通常有全库读权限,泄露面等于数据库本身,放进密钥管理而不是脚本明文。
- PITR 依赖连续的日志归档:归档中断后 RPO 就是"中断那一刻",要监控归档延迟(WAL 滞后量)而不只是监控备份任务退出码。
- 逻辑备份导出耗时随数据量超线性增长,几百 GB 以上的库继续用 pg_dump/mysqldump 会让备份窗口失去控制,该切物理备份就切。
- 加密备份文件前先想清楚恢复端怎么解密:密钥丢失等于备份丢失,密钥要有独立于备份介质的保管方案。
- 恢复演练要贴近真实容量:在 4GB 内存的小虚机上演练 TB 级恢复,结论完全不适用于生产。
- 本文的 PostgreSQL/MySQL 命令基于长期稳定的主版本接口,具体参数以你所用大版本的官方文档为准;工具行为(如 rsync 的差异算法)升级前后可能有细节差异。
- 备份的成功邮件不代表成功:只有"恢复验证通过"才算,告警条件要挂在验证步骤之后。
总结
备份体系的设计顺序:RPO/RTO 定频率与形态 → 全量/差异/日志归档组合覆盖恢复粒度 → 一致性靠数据库机制而不是 cp → 验证分三层、可恢复性必须演练到业务可用 → 异地 3-2-1 且凭证隔离 → GFS 控制成本。其中真正区分"有备份"和"有备份能力"的只有两件事:验证和演练。从今天起把"备份成功"的告警改到"恢复验证成功",这一行的改动就是整个体系里最值钱的一次。
参考
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。