备份任务天天跑、成功率 100%、告警一直安静——然后在真需要它的那天,恢复出来的实例起不来,或者起来后数据停在三天前。共同点不是工具坏了,而是验证对象选错了:团队验的是"备份进程退出码",业务要的是"恢复出一条可用数据链"。
这篇只谈三件事:场景与能力如何配对、可恢复性用什么判据证、验证要付多少代价。
1. 三种场景,三种完全不同的能力
| 场景 | 典型起因 | 需要的备份类型 | 演练难度 |
|---|---|---|---|
| 单表误删 | 脚本写错 WHERE |
逻辑备份或延迟从库 | 低 |
| 整库损坏 | 磁盘故障、升级失败 | 物理全量 + 增量链 + binlog | 中 |
| 机房级故障 | 区域故障、云盘异常 | 异地副本 + 独立启动依赖 | 高 |
第一个设计错误是一套备份应付三种场景:物理全量救不了"把一张表退回 10:05",逻辑导出在大库上恢复全库常超出 RTO。三者须分别回答"靠什么恢复"并分别演练。
2. RPO / RTO 要写成业务语言
定义人人会背,错在写成了平均值、写成了单库口径。
| 常见写法 | 问题 | 可执行写法 |
|---|---|---|
| "RPO 5 分钟" | 平均还是最坏? | "故障时最多丢最近 5 分钟写入(按最坏值)" |
| "RTO 1 小时" | 从发现算还是决策算? | "确认需恢复起 60 分钟可读、120 分钟可写" |
| "数据库有备份" | 未覆盖缓存与配置 | "丢失清单:MySQL 全库、Redis 可重建、对象存储按日增量" |
| "有异地备份" | 未验证能否独立启动 | "异地副本不依赖主机房即可启动" |
写不出可执行版本,通常说明指标从未被真正需要过;写的过程也暴露了顺序问题。
3. "备份成功"的判据不是退出码
| 判据层 | 能证明 | 不能证明 |
|---|---|---|
| 退出码 0 | 命令跑完了 | 文件完整、能导入 |
| 校验和匹配 | 传输无损 | 语义正确、版本兼容 |
| 恢复到临时实例 | 可恢复 | 数据完整、业务可用 |
| 抽样比对通过 | 关键数据在 | 全库一致 |
| 业务方跑通一条关键路径 | 业务可用 | — |
只有最后一层算可用。前五层是必要条件,常见做法是自动化到第三层就收工——而第三层最容易满足、信息量最少。
4. 一致性:方式 × 写入状态的组合属性
| 备份方式 | 写入状态 | 一致性来源 | 一致性边界 |
|---|---|---|---|
| 逻辑导出(快照事务) | 持续写入 | 可重复读快照 | 只对事务引擎有效 |
| 逻辑导出(锁表) | 停止写入 | 表锁 | 期间业务不可写,窗口极短 |
| 在线物理热备 | 持续写入 | 脏页/redo 跟踪 | 恢复前必须做一致性回放 |
| 冷拷文件 | 停服务 | 停止即一致 | 仅限停机窗口 |
| 卷级快照 | 持续写入(瞬时冻结) | 卷级原子点 | 多卷逐个不原子 |
关键是一致性边界写在哪。说明里只有"每天 3 点全量"、没写"该时点是否有业务在写、非事务表是否包含",恢复的库是否自洽就是未知的。
# 一致性点取证:备份前后各记一次位点,与元数据一起归档
mysql -e "SHOW MASTER STATUS\G" > /backup/pos_begin.txt
mysql -e "SHOW MASTER STATUS\G" > /backup/pos_end.txt
5. 可恢复性验证检查什么
| 检查项 | 方法 | 抓到的缺陷 |
|---|---|---|
| 行数对比 | 两侧按表计数 | 表缺失、导入中断 |
| 关键表抽样 | 按主键取最新 N 条逐字段比 | 精度、时区偏移 |
| 外键与约束 | 约束存在、FOREIGN_KEY_CHECKS 已恢复 |
静默失去完整性 |
| 自增/序列 | 当前值 ≥ 最大主键 | 新写入撞主键 |
| 账号与授权 | 应用账号存在、权限正确 | 恢复后应用连不上 |
mysqldump --no-data --skip-comments db | sed 's/ AUTO_INCREMENT=[0-9]*//' > /tmp/schema.sql
mysql -N -e "SELECT table_name, table_rows FROM information_schema.tables\
WHERE table_schema='db'" > /tmp/count.txt # 两侧各跑一次,diff 须为空
结构比对要先抹掉 AUTO_INCREMENT 这类易变值,否则每次 diff 都非空,团队会当噪声忽略。
6. 备份文件本身的可读性
| 依赖 | 失效方式 | 需固化在备份旁边 |
|---|---|---|
| 加密密钥 | 与备份同机,一起丢 | 独立保管 + 取用记录 |
| 备份工具版本 | 新版产出、旧版读不了 | 版本写入元数据 |
| 字符集/排序规则 | 恢复后乱码或排序变化 | 导出时的字符集参数 |
最难的是密钥:既要与备份分离(避免一并丢失),又要在需要时取得到(避免"安全到自己打不开")。做法是存独立系统并纳入演练——不带密钥的演练等于没演练。
7. "异地"的真实含义
| 位置 | 算异地吗 | 判据 |
|---|---|---|
| 同机另一块盘 | 否 | 同故障域 |
| 同机房另一台 | 否 | 同电源、同出口 |
| 同城另一机房 | 弱 | 挡单机房,挡不住区域网络 |
| 跨区域/跨云 | 是 | 能否独立启动仍需验证 |
判据不是距离,而是故障域是否独立、恢复时是否仍需源端。陷阱:异地副本靠增量投递,恢复要问源端要基线——源端没了,异地也起不来。
8. 备份窗口与业务高峰
| 冲突 | 表现 | 处理方向 |
|---|---|---|
| 与高峰重叠 | IO/网络被抢占 | 限速、错峰、增量替全量 |
| 与批处理重叠 | 两件重 IO 互放大 | 调度层串行,而非都"避开白天" |
| 冻结时间过长 | 写入挂起,请求超时 | 缩短窗口,接受多卷非原子 |
判据朴素:备份窗口的最坏耗时必须小于业务可接受的减速时长,而非"平均看着还行"。
9. 存储成本与保留期的账
| 策略 | 空间放大 | 恢复链条 | 成本结构 |
|---|---|---|---|
| 每日全量 | 高(N 天 N 份) | 1 | 空间贵、恢复简单 |
| 全量 + 每日增量 | 低 | 1 + N | 空间省、链条长 |
| 归档层长期保留 | 极低单价 | 取回有延迟 | 取回时间要计入 RTO |
压缩比按格式核对(文本型收益明显,已压缩的二进制几乎没有),别照搬倍数。账可写成一句:"保留 90 天"的真实成本 = 空间 × 策略 × 取回延迟的业务代价,最后一项最常漏——归档取回可能小时级,RTO 却是分钟级。
10. 备份被删掉的风险
| 风险 | 机制 | 隔离手段 |
|---|---|---|
| 运维误删 | 通配符过宽、路径打错 | 目录只读、清理走白名单 |
| 入侵/勒索 | 有写权限即可删 | 不可变存储、对象锁、离线副本 |
| 演练覆盖生产 | 脚本指向生产 | 隔离目标 + 二次确认 |
最后一条不是理论风险:一个环境变量没覆盖、白名单没加,备份就灌进了生产。
mount -o remount,ro /backup # 备份目录只读挂载,删除前必须先解挂
11. 恢复时的应用协同
| 顺序 | 组件 | 为什么这个顺序 | 停写要求 |
|---|---|---|---|
| 1 | 数据库 | 一切状态之源 | 是,且要有停写证据 |
| 2 | 缓存 | 缓存里是旧值,必须先清 | 先清后放流量 |
| 3 | 消息/队列 | 积压需与新数据对齐 | 停消费,处理重复 |
| 4 | 搜索索引 | 从数据库重建,不单独恢复 | 重建期降级查询 |
| 5 | 应用实例 | 最后启动,避免向坏库写入 | 全恢复前不接流量 |
# 停写的证据:位点不再前进 + 写入 QPS 归零 + 业务方确认,三条都要有
mysql -e "SHOW MASTER STATUS\G" | grep Position
redis-cli --scan --pattern 'order:*' | xargs -r -n 100 redis-cli DEL # 分批清缓存
一个常被踩的细节:"停写"不能靠"我已经通知了",只能靠证据。否则恢复完立刻被写脏。
12. 演练的频率与范围
| 形态 | 覆盖 | 成本 | 频率 |
|---|---|---|---|
| 抽样单表恢复 | 逻辑备份可用性、熟练度 | 分钟级、单人 | 每两周 |
| 单实例整库恢复 | 物理链、增量链、版本 | 小时级、需隔离环境 | 每月 |
| 全链路接管(含应用) | 顺序、缓存、切流量 | 半天、跨团队 | 每季度 |
| 不带密钥的盲演 | 流程是否自洽 | 半天、暴露隐性知识 | 每半年 |
# 演练脚本的第一行必须是目标守卫:指向生产就直接退出
[ "${RESTORE_TARGET}" = "prod" ] && { echo "refuse"; exit 1; }
只做抽样恢复会得到错觉:文件没问题、流程也没问题。真实事故最常见的失败点是"恢复后没人知道该清缓存、该按什么顺序放流量"——这类知识只能靠全链路演练获得。演练还要换人执行:写脚本的人会跳过他自己知道、别人不知道的步骤。
13. 度量:两个数字就够
| 指标 | 定义 | 判据 |
|---|---|---|
| 最近成功恢复时间 | 真恢复出可用实例的时刻 | 超过保留期一半即风险 |
| 演练覆盖率 | 已演练/全部资产 | 关键资产 100% |
| 增量链完整率 | 链条不断裂的备份集占比 | 任何时候都须 100% |
指标不必多,但必须有一项端到端(成功恢复时间),否则度量会退化成"备份任务成功率"这个自我安慰的数字。
14. 排错:八种"备份在、恢复不了"
| 现象 | 原因 | 处理 |
|---|---|---|
| 恢复实例起不来 | 物理备份未回放一致性 | 补 redo 回放 |
| 导入报语法或模式错 | 版本或 sql_mode 不同 |
恢复前对齐会话参数 |
| 数据停在更早时刻 | 增量断档或时点参数错 | 校验父节点,按位点重放 |
| 新写入撞主键 | 自增计数器未对齐 | 恢复后校正计数器 |
| 演练覆盖了生产 | 脚本未隔离目标 | 默认隔离 + 生产二次确认 |
15. 边界:这些数据不必做完整策略
| 数据 | 更合适的做法 | 理由 |
|---|---|---|
| 可从上游重新同步的派生数据 | 只备份生成逻辑与配置 | 重建成本低于备份成本 |
| 缓存类数据 | 不备份,冷启动重建 | 恢复旧缓存反而有害 |
| 有严格版本的代码与配置 | 版本库即备份 | 无需另建文件级备份 |
判据一句话:能重建的不需要备份,不能重建的才需要验证。两类混在一套策略里,重要的部分会被稀释。
16. 自测
- 退出码 0、校验和匹配,还差哪一层才能称"备份可用"?
- 单表误删与整库损坏各依赖哪种备份?为什么一套应付不了两种场景?
- 在线物理热备的"一致性"由什么决定,恢复前还缺哪一步?
- 为什么"同城另一机房"只能算弱异地?
- 恢复顺序里缓存为什么必须排在应用之前且先清?
- 只做抽样恢复的团队,最可能缺哪类知识?