备份与恢复:恢复点、数据一致性与业务切换
备份保存可供恢复的数据版本。恢复则把这些版本重新组成可运行的系统,并确认业务能够从选定的位置继续。数据库、上传附件、访问凭据和应用配置分散在不同地方,只恢复其中一项,订单查询仍可能缺图片、连错服务,或者重复执行已经完成的付款任务。
准备恢复方案时,先确定要恢复哪些业务、允许丢失多长时间的数据,以及业务最多能中断多久。随后才能选择备份方式、保存频率和恢复环境。
确定恢复对象与可接受的数据损失
一份业务数据分布在哪里
以带附件和异步通知的订单服务为例,恢复对象可以按用途拆开:
订单服务
├─ 业务事实:订单、支付记录、库存流水、幂等记录
├─ 文件对象:附件、对象版本、数据库中的文件引用
├─ 异步状态:待发送事件、消息消费进度、补偿任务
├─ 运行材料:应用制品、配置版本、数据库变更脚本
├─ 访问能力:数据库账号、加密密钥、仓库与备份库凭据
└─ 可重建数据:部分缓存、检索索引、派生报表“可重建”需要有完整来源和可接受的重建时间。缓存里如果只存商品详情,可以从数据库重建;如果还存没有落库的扣减结果,直接清空就会丢业务状态。检索索引可以重建,也要安排全量导入、增量追平和查询切换,避免重建期间漏掉新数据。
跨系统引用需要一起考虑。数据库快照可能包含一条刚写入的附件地址,而对象备份尚未保存该文件;也可能只恢复了附件,订单引用却停在更早的版本。应记录各系统的恢复位置,用暂停相关写入、对象版本保留或业务对账解决这段差异。分别完成的两个备份不会自然形成同一时刻的业务快照。
RPO 与 RTO 怎样影响方案
RPO(恢复点目标)约束可接受的数据损失时间,RTO(恢复时间目标)约束从中断到恢复约定服务水平的时间。设定时要说清业务范围,例如“订单写入”和“历史报表查询”可以有不同目标。
每小时做一次快照,故障发生在下一次备份之前,损失可能接近一个周期。备份失败、日志归档滞后和发现延迟还会扩大损失。因此要观察最新可恢复位置距当前写入位置有多远,而非只看任务频率。
恢复用时通常由几段组成:
发现与决策
→ 准备实例和访问权限
→ 下载、解密、校验备份
→ 导入全量与重放日志
→ 对账、应用验证
→ 切换流量并恢复任务实测下载、导入和日志重放速度后,才能估计数据增长对恢复时间的影响。恢复环境需要临时容纳压缩包、解密后的文件、数据库和索引,所需磁盘往往超过备份包大小。演练记录应保留每段用时;仅统计 SQL 导入时间会漏掉大量实际停机时间。
全量、增量、复制与快照
全量备份保存一个完整基线。增量保存相对于某个基线或前次增量的变化,恢复时必须知道它依赖哪些文件。数据库事务日志把基线向前推进到指定位置;若中间缺一段,后面的日志通常无法补上这个缺口。MySQL 时间点恢复说明描述了先恢复全量、再应用二进制日志的基本过程。
副本用于提高可用性,但误删也会沿复制链传播。文件镜像同步同样会传播覆盖或删除。保留历史版本、隔离删除权限、建立异地副本,分别用于应对这些风险。
存储快照保留某个时刻的块或文件系统状态。它能否用于数据库恢复,取决于数据库刷盘与恢复协议、多个卷是否同时快照,以及日志是否齐全。运行中的数据库卷即使以只读方式挂载给备份容器,原数据库仍能修改它;tar 读到的可能是不同时间的文件内容。Docker 的卷备份与恢复负责文件搬运,数据库的一致性条件仍需另外满足。
选择备份方式并保存完整恢复材料
数据库使用自己的备份协议
逻辑备份保存表结构和数据,便于检查、选择对象与迁移;恢复时需要重新执行 SQL、建立索引。物理备份保存数据库文件,常用于较大的数据量,但依赖更严格的产品版本、文件格式和准备流程。MySQL 备份方法列出了这些方法的适用条件。
MySQL 的 mysqldump --single-transaction 对 InnoDB 建立一致性读取。备份期间应避免对相关表执行会破坏快照条件的 DDL;MyISAM、MEMORY 等表需要另行安排锁定或停写。使用 --source-data=2 时,导出文件中包含与备份协调取得的 binlog 坐标,命令可能在开始阶段短暂获取全局读锁。应提前确认权限与长事务影响,不能事后独立查一个日志位置拼在备份旁边。具体权限和选项组合见 mysqldump 手册。
GTID 环境还要处理已执行事务集合和目标实例状态。--set-gtid-purged 的取值影响导入后的 GTID 信息;不能把关闭 GTID 的教学实验直接套在已有复制拓扑中。备份工具与服务端的版本、GTID 模式、字符集、时区、加密插件及 keyring 位置,都应进入恢复记录。
PostgreSQL 的 pg_dump 保存单库一致性视图,角色和表空间属于集群级对象,需要另行保存。文本备份通过 psql -X --set ON_ERROR_STOP=on 导入可以在 SQL 错误时停止;已经执行的操作是否回滚,还取决于事务包装。自定义格式使用 pg_restore,详细用法见 PostgreSQL SQL Dump。
PostgreSQL 时间点恢复使用基础备份与连续 WAL,按目标时间、事务或恢复位置推进。WAL 必须来自匹配的数据库集群并覆盖所需区间;切换时间线后还要保留相应历史文件。pg_dump 不能充当这条物理恢复链的基础备份。配置与目标选择见 PostgreSQL 连续归档与 PITR。
Redis 与派生数据
RDB 是一个时间点快照,AOF 记录写入变化。选择恢复来源时,应检查目标实例启用了哪些持久化方式;同时启用时,启动通常优先加载 AOF,单独替换 RDB 可能没有得到预期数据。
备份 Redis 7 及以后的多文件 AOF,要保留 manifest 与其引用的文件,并协调重写过程。正在重写时随意打包目录可能得到不完整文件组合。RDB 文件的完成状态、AOF 重写协调及较新版本的备份命令入口,均以所用版本的 Redis 持久化文档为准。
恢复后检查关键键的类型、内容和 TTL。带过期时间的数据可能在恢复启动时已经过期;持久化文件有该键,并不意味着业务还能继续使用它。纯缓存可以安排预热,而会话、限流状态、分布式锁或未落库任务应分别评估。
具体部署、身份与持久化操作可查阅 MySQL、Redis、消息队列与对象存储。恢复消息消费进度前,要确认消息是否仍在保留期内,以及重复处理会不会再次扣款或发送通知。
文件、配置与密钥怎样一起保管
上传文件应先取得稳定的文件集合:停止相关写入、使用产品提供的导出,或从已协调完成的快照中读取。普通文件归档要保留相对目录、权限和必要的属主信息;使用 ACL、扩展属性、SELinux 的系统还要检查这些元数据是否一并保存、恢复。备份和恢复工具的选项必须成对验证。
归档放在源目录之外,避免把正在增长的备份包再次收入自身。恢复到新的空目录,先查看路径和类型,再检查权限与应用可读性。不要直接解包到 /、正在使用的数据库目录或原有生产挂载点。只读副本有助于保护原件,待确认的恢复操作都在工作副本上进行。
应用制品和配置也需要版本关系。保存 Compose、systemd、Nginx 和数据库迁移文件时,记录它们对应的发布版本。nginx -T 展示磁盘配置展开结果,仍需用实际请求确认运行进程采用了什么配置;其输出还可能含内部地址和敏感参数。
备份本身含业务数据,数据库密码、TLS 私钥和应用加密密钥更需受限保存。传输加密只能保护传输过程,落盘后仍需加密、最小读取权限和访问记录。解密密钥应有独立的恢复安排,不能只放在可能一起损坏的应用主机上。使用 GnuPG 时,密钥导出与恢复选项可查官方密钥备份说明。
同一个应用账号如果既能写业务库,又能删除全部备份,就会让单个账号失陷影响所有副本。可以分离备份写入、恢复读取和历史删除身份,再结合异地保存、保留锁或离线副本。保留锁的期限与解锁方式需要在启用前确认,避免备份尚不可恢复就清理旧版本,也避免存储费用失控。
每次备份应留下什么
| 材料 | 用途 |
|---|---|
| 业务范围、源实例标识 | 防止拿错库、错租户、错环境 |
| 服务端与备份工具版本、必要插件 | 重建兼容运行环境 |
| 一致性方式、全量基线、日志起止位置 | 确定可恢复区间及依赖关系 |
| 文件列表、大小、SHA-256、加密信息 | 检查传输完整性并取得解密能力 |
| 备份开始、完成与最新归档位置 | 判断耗时和恢复点新鲜度 |
| 最近隔离恢复结果、业务核对方法 | 了解该组合实际能恢复什么 |
校验和应与可信记录比较。若攻击者能同时替换备份和校验清单,单纯重新计算 SHA-256 无法识别伪造。可以把清单放到独立权限控制的位置,或使用已核验签名。
自动任务宜先写 .partial 文件,成功退出且校验后再改为正式名称;失败文件不加入可用备份集合。监控最近一次成功备份、归档滞后、连续失败和存储增长,失败通知还应有备份系统之外的接收路径。保留策略按整条恢复链删除:保留一个增量而删掉它依赖的全量没有意义。删除前先列出候选集合并确认新基线可恢复,不直接复制一条按文件年龄清理的命令。
在隔离实例中恢复到误操作之前
实验环境与数据变化
使用 MySQL 恢复实验包。在 Linux 普通用户拥有的独立目录解压,准备 Bash、OpenSSL、sha256sum、Docker Engine 和约 4 GB 可用空间。宿主用户需要 Docker 操作权限,这种权限足以控制宿主,应仅在测试机器使用。数据库镜像固定为 mysql:8.4.11。
官方精简镜像没有包含 mysqlbinlog。实验的 Dockerfile 单独构建 local/ops9-mysql-tools:8.4.11:从同版镜像取得系统环境和签名密钥,在工具镜像内移除精简服务端包,再从 MySQL 8.4 仓库安装完整客户端。数据库实例继续使用原镜像,不修改已启动实例的软件包。仓库配置与版本选择可查 MySQL Yum 仓库指南。首次构建需要连接镜像仓库和软件源;受限网络应按离线部署先构建并导入这两份镜像。
实验创建 ops9-pitr-source、ops9-pitr-target 两个容器及同名数据卷。它们不发布宿主端口,使用 --network none,不会连接外部数据库。容器入口初始化时使用 root,数据库进程随后以镜像的 mysql 用户运行;管理命令通过容器内受限客户端配置使用数据库 root,仅服务于这两个实验实例。
随机凭据放在私有 runtime 目录,客户端配置复制到容器内并设为 0600,不将密码写入命令参数。实验关闭 GTID、只有一个业务库和一个写入者,使用 ROW binlog。这些约束让日志区间可以直接观察,不涉及复制拓扑重建。
源实例的事务顺序
订单 1(100 元)
↓ 全量备份 B,记录起始日志位置
订单 2(200 元)
↓ 恢复截止位置 T:位于两个完整事务之间
误删全部订单
订单 3(300 元)
恢复实例:B + [B, T) 的事务 → 订单 1、2
保留源实例:用于核对误操作、订单 3 与后续业务在解压目录按阶段执行,任何命令非零退出都先停下检查:
bash lab.sh setup
bash lab.sh backupsetup 应输出 READY,backup 应输出 BACKUP。已有同名容器、卷或 runtime 目录时脚本拒绝覆盖。初始化超时先检查 docker ps -a 与对应容器的私有日志;不要把日志和凭据目录公开上传。
全量备份与日志坐标必须匹配
备份阶段的关键命令如下,标准输出由 Bash 写入本地临时文件:
docker exec ops9-pitr-source mysqldump \
--defaults-extra-file=/tmp/lab-client.cnf \
--single-transaction --source-data=2 --set-gtid-purged=OFF \
--no-tablespaces --routines --events --triggers --skip-dump-date \
--databases recovery_lab > runtime/base.sql.partial脚本确认命令成功和文件非空后才改名为 base.sql,提取其中的 SOURCE_LOG_FILE、SOURCE_LOG_POS,并生成校验清单。--databases 包含业务库的创建与选择语句;这里不导出系统账号库。--no-tablespaces 避免导出表空间定义,但并未免除其他对象所需权限。
查看本机生成的坐标与完整性结果:
cat runtime/start.txt
(cd runtime && sha256sum -c SHA256SUMS)第一条应显示 binlog 文件名与数字位置,以本次输出为准;第二条应为 base.sql: OK。快照过程中发生 DDL、导出失败或坐标缺失时,不能进入下一步并把该文件作为基线。
制造已知误操作并选定事务边界
bash lab.sh damage该步骤先提交订单 2,再记录日志位置 T,然后误删实验表中的订单,最后新增订单 3。源实例最终只剩 3 / 300.00。脚本轮转 binlog 后,复制已经关闭的日志文件到本地,避免直接复制仍在写入的文件。
这里能提前取得 T,是因为实验没有其他写入者。真实误操作发生后,需要从审计信息、业务标识和 binlog 事件定位整个错误事务,找到它开始之前的完整事务边界。时间戳适合缩小搜索范围;时区、同一秒的多笔事务和长事务都会影响精确选择。MySQL 按事件位置恢复给出了定位方式。
用 mysqlbinlog --base64-output=DECODE-ROWS -vv 可以阅读行事件,但这份注释化输出用于分析,不作为恢复 SQL 执行。重放时保留正常的 BINLOG 事件输出。--stop-position 排除位置达到或超过指定值的事件;若输入多个文件,起始位置作用于第一个文件、截止位置作用于最后一个文件,文件次序与中间连续性都要核对。详细行为见 mysqlbinlog 手册。
导入空实例并核对业务数据
bash lab.sh restore恢复阶段先校验文件,确认目标容器属于本实验且尚不存在 recovery_lab,然后生成重放 SQL,再导入全量与指定日志区间。关键的区间生成命令是:
read -r file start < runtime/start.txt
read -r stopfile stop < runtime/stop.txt
# lab.sh 还会检查文件名、数字格式、同一日志文件和 stop > start。
docker run --rm --network none \
--mount "type=bind,src=$(pwd -P)/runtime,dst=/backup,readonly" \
local/ops9-mysql-tools:8.4.11 \
--start-position="$start" --stop-position="$stop" \
"/backup/$file" > runtime/replay.sql.partial日志工具容器使用默认 root 身份,只读挂载实验材料,关闭网络,不挂载数据库卷或 Docker socket。日志转换和数据库导入分别检查退出状态,避免管道末端成功掩盖前段失败。预期结果:
BASE: 1 row, sum 100.00
PITR: 2 rows, sum 300.00; order 3 is outside the recovery point只导入全量时有订单 1;重放后增加订单 2,错误删除没有执行。订单 3 虽然是有效写入,但发生在 T 之后,也被排除。整库退回旧时间点会一并排除那之后的正常业务,因此需要保留故障源、外部流水和消息记录,评估订单 3 应如何补回。
导入中途报错时,目标实例可能已经部分写入。先保留错误与失败位置,在另一空目标重新恢复;不要添加“忽略错误”选项反复导入,也不要对源实例执行清库。
重启复验与损坏副本负例
bash lab.sh verify脚本重启恢复实例,再次核对订单 1、2 和金额总和;另外复制备份文件到 runtime/corrupt-check 并修改副本,确认校验失败。原备份、原日志及恢复库保持不变。成功时输出:
PASS: restart preserved recovery; corrupt copy rejected (original files unchanged)行数与总额适合这个小实验。真实订单库还应核对主键集合、金额币种、状态分布、关联流水和抽样明细。相同行数或相同总额可能掩盖不同的错误,需要选择能反映业务约束的检查。
完成后停止本实验:
bash lab.sh stop该命令保留容器、数据卷和本地文件。再次演练可在新解压目录设置不同的 LAB_ID,例如 export LAB_ID=ops9-pitr-second,后续各步保持同一值;容器与卷名称随之变化。脚本不会自动删除卷或覆盖已有恢复库。
从恢复完成到业务重新可用
隔离环境先关闭外部副作用
恢复的数据库可能保存着待执行任务、支付回调地址、短信账号和有效生产凭据。启动应用验证前,先限制网络出口,替换外部依赖凭据,关闭自动任务、消费者与补偿调度。恢复环境应使用自己的注册、配置和存储地址,避免恢复应用刚启动就接入原服务集群。
验证可以逐层推进:数据库完成恢复并能查询;应用使用目标 schema 启动;关键读取与受控写入符合预期;文件引用、异步任务和外部流水能够对上。恢复环境中的写入使用独立测试账号与模拟渠道,不直接向真实用户发送短信或付款。
切换时要确定唯一写入端
系统恢复到 T 后,原系统可能仍在接收请求。直接修改应用连接地址会让新旧两端同时写入,形成两套变化历史。切换方案应明确暂停哪些入口、消费者和调度器,怎样确认原写入端已被隔离,以及何时放开新写入端。连接池、DNS 缓存、长连接和旧实例都要纳入检查。
常见选择有两种。若只有少量订单误删,可以从隔离恢复库提取目标记录,对比原库中后续修改,再按业务规则补回;若整个存储不可用,则准备完整替代实例,验证后切换服务。前者需要处理并发修改与重复流水,后者需要接受恢复点后的数据差异并安排追补。
新实例一旦接收写入,直接切回旧实例就会丢掉新写入。回切需要另行同步或对账。操作系统、应用版本和数据恢复的回退条件应分别记录,不能用一次镜像回滚代替数据处理。
常见失败的处理方向
| 现象 | 先确认 | 后续动作 |
|---|---|---|
| 文件校验失败或无法解密 | 副本是否完整、清单来源、密钥版本 | 保留损坏副本,获取另一份可信副本;不跳过验证 |
| 全量可用但 binlog/WAL 有缺口 | 最后连续位置、归档任务与保留策略 | 重新计算可恢复范围,由业务决定能否接受;不能跨缺口硬接 |
| 导入报版本、插件或对象错误 | 工具/服务端版本、字符集、keyring、权限 | 在匹配环境重做恢复,保留失败目标供分析 |
| 数据恢复后应用无法启动 | 配置、账号、schema、外部服务 | 使用对应发布材料逐项确认,勿直接复制生产密钥联调 |
| 恢复后出现重复通知或扣减 | 任务状态、幂等记录、消费进度 | 暂停相关执行器,对账后按业务标识补偿 |
| 恢复耗时超过目标 | 下载、解密、索引、日志重放各段耗时 | 调整备份方式、基线频率或恢复资源,并重新计时 |
演练覆盖哪些故障
定期从新环境恢复,能够发现旧主机隐藏提供的账号、工具、配置和密钥依赖。演练至少轮换覆盖单表误删、整实例丢失、备份损坏、日志归档中断,以及无法访问原仓库或备份账号的情形。跨机房恢复还涉及网络路由、域名、对象副本与访问身份,应在专用测试资源中验证。
保留选定的恢复点、接受的数据差异、分阶段耗时、查询结果和重新开放写入的条件。发现备份缺少密钥、恢复步骤依赖已下线镜像,或者消息补偿没有负责人时,修正后重演该分支。故障期间的人员分工、通知与切换决策见应急预案,日常备份新鲜度检查见生产巡检。
