数据与备份:3-2-1、加密、离线副本与恢复演练

备份与数据库在同一个账号,是独立开发者最常见的假安全。本篇拆解 3-2-1-1-0、按数据分级定 RPO/RTO、备份对象清单、加密与不可变存储、成本控制,以及一次计时的恢复演练和三种故障场景。

备份的价值不在于「我有一份」,而在于「我能在需要的时候,用它恢复到一个可用的状态」。前者是存储,后者才是备份。

一、误删之后的四十八小时

这是为解释机制构造的假设案例,人物、引语、收入和耗时均为教学设定,不是对真实事件的调查记录。

一位开发者给自己的 SaaS 做数据库清理,想删掉测试环境里的一张表。终端窗口开在两个标签页,他删掉的是生产环境。

数据库开启了每日自动备份,保留 30 天。看起来没问题。但恢复时他发现:

  • 自动备份和数据库在同一个云账号、同一个区域。
  • 备份是「实例级快照」,恢复只能整实例恢复到某个时间点,不能只恢复一张表。
  • 他需要把整个实例恢复到一个新实例,再从新实例导出数据,最后导回生产。
  • 整个过程他做过一次,但那是两年前,步骤已经忘了。

四十八小时后,数据恢复了。丢的是最后一次快照之后的几个小时订单。他说:

「我有备份,但我没有恢复能力。这两件事之间,差了一次演练。」

二、3-2-1 与 3-2-1-1-0

2.1 经典 3-2-1

数字含义个人产品的落地
3至少三份数据生产 + 备份 + 归档
2至少两种介质云存储 + 本地硬盘 / 对象存储 + 磁带
1至少一份异地不同区域或不同供应商

2.2 增强版 3-2-1-1-0

数字含义为什么重要
3三份数据防单点丢失
2两种介质防同一类介质同时损坏
1一份异地防区域性故障
1一份离线或不可变防勒索软件与误删同步
0零错误,经过验证防「备份存在但不可用」

最后两个数字是个人产品最容易漏掉的。离线/不可变防的是「攻击者或你自己把备份也删了」;零错误防的是「备份文件损坏、权限不对、恢复脚本失效」。

三、按数据分级定 RPO/RTO

不是所有数据都值得同样的投入。先分级:

数据等级内容RPORTO备份方式
A:不可再生用户数据、订单、支付记录、许可证≤ 1 小时≤ 4 小时持续归档 + 时间点恢复
B:业务关键配置、内容、邮件、分析数据≤ 24 小时≤ 24 小时每日全量 + 增量
C:可重建缓存及已证明可重建的数据业务可接受的丢失范围已测得重建时间发布产物、审计日志如不可重建则另分级
D:合规保留审计日志、发票、合同不丢失可检索归档存储,长期保留

先写 A 级数据的 RPO/RTO,再决定备份频率。 如果 A 级 RPO 是 1 小时,每日一次的全量备份就不合格。

四、备份对象清单

很多人的备份只覆盖数据库,漏掉了「恢复时必须一起回来」的东西。

对象常见位置备份方式备注
关系型数据库云数据库 / 自建快照 + 逻辑导出 + WAL/binlog逻辑导出用于单表恢复
对象存储具体桶及供应商核验该产品的复制、版本/保留能力,另留独立副本S3 兼容接口不代表能力相同
配置文件环境变量 / 配置中心加密导出与代码分开存放
密钥与证书密码管理器 / KMS离线加密副本不要只存在一个 KMS
日志日志服务归档到冷存储事故取证需要
许可证与订单支付平台 / 自建定期导出 CSV平台受限时的对账依据
邮件邮箱服务商定期导出 mbox客户沟通与合同证据
DNS 区域DNS 托管商导出区域文件换托管商时必需
分析数据分析平台定期导出避免平台锁定

五、备份方式的选择

方式优点缺点适用
实例快照快、完整、可整机恢复粒度粗、跨区域可能额外收费A 级数据的日常保护
逻辑导出可单表恢复、可跨版本慢、占空间、恢复需重建迁移与单表修复
时间点恢复(PITR)可恢复到秒级依赖 WAL/binlog 保留A 级数据的兜底
文件同步简单误删会同步、可能被加密不适合作为唯一备份
对象存储版本控制防覆盖与误删需要管理生命周期文档、配置、产物
离线硬盘隔离性好需要人工、易忘记年度归档

至少组合两种。 例如:云数据库快照(日常)+ 每日逻辑导出到另一个账号的对象存储(可单表恢复)+ 每月离线归档(防勒索与误删)。

六、加密与密钥分离

备份里往往包含用户数据、密钥、合同。加密是必须的,但加密方式决定你能不能在事故中恢复。

6.1 三条原则

  1. 备份加密,传输加密,存储加密。 三者不是一回事。
  2. 加密密钥不要和备份放在同一个地方。 备份在云 A,密钥至少在云 B 或离线。
  3. 密钥丢失等于备份丢失。 密钥也要有恢复副本,且要有人知道怎么用。

6.2 常见结构

结构密钥存放风险
云厂商托管密钥同一云账号的 KMS账号失守,备份与密钥一起没
自管密钥 + 云存储密码管理器 / 离线密钥丢失则无法恢复
客户端加密后上传本地密钥 + 离线副本操作复杂,容易忘

个人产品推荐第二种:备份上传前在客户端加密,密钥存密码管理器并留一份离线。 这样云厂商看不到明文,账号失守也不至于直接泄露数据。

七、不可变与删除保护

勒索软件和误操作的第一反应都是「删备份」。所以备份系统要能抵抗「有权限的人删除」。

机制作用注意
对象存储版本控制保留历史版本需要设置生命周期,避免无限增长
对象锁(Object Lock)在保留期内不可删除选合规模式还是治理模式要清楚
快照保留策略防止快照被批量删除保留策略本身也要保护
跨账号复制生产账号失守不影响备份账号备份账号不要用同一组凭据
MFA 删除删除需要第二因素紧急时可能阻碍恢复
离线副本物理隔离需要定期更新

最低要求:备份存储在一个与生产不同的账号,且生产账号的凭据不能删除备份。

八、恢复演练:每季度一次

没有演练的备份,只能算「希望」。

8.1 演练清单

  • 选一个 A 级数据对象(例如订单表)。
  • 从备份恢复到一个隔离环境,不影响生产。
  • 记录开始时间、恢复完成时间、数据截止时间。
  • 校验行数、关键字段、金额合计。
  • 记录遇到的权限、版本、依赖问题。
  • 更新恢复手册。
  • 把演练结果写进日历,下次继续。

8.2 演练指标

指标计算目标
实际 RTO恢复完成时间 − 发现时间≤ 设定目标
实测数据损失窗口故障时点 − 可恢复的一致性业务截止点与 RPO 目标比较;非单看最大时间戳
一次成功率第一次尝试即恢复成功尽量为 1
人工步骤数需要手动执行的步骤越少越好

8.3 三种演练场景

场景验证什么
单表误删逻辑导出是否可用、恢复粒度是否够细
整库损坏快照 + PITR 是否可用、跨区域恢复是否可行
账号失守备份账号是否独立、凭据是否隔离

九、成本控制

备份会持续花钱。三个控制手段:

  1. 分层存储:最近 30 天用标准存储,3 个月后转低频,1 年后转归档。
  2. 保留策略:示例可为可恢复运行数据保留 90 天、一般配置 30 天;法定会计档案、合同证据和隐私删除须分别确定,不能由 A/B/C 标签自动决定。
  3. 去重与压缩:逻辑导出时压缩,快照利用云厂商的增量能力。

不要为了省钱砍掉「异地」和「离线」。这两项恰恰是事故时最值钱的。

十、常见错误

错误后果更好的做法
备份与生产同账号账号失守,备份一起没跨账号、跨供应商
只做快照,不做逻辑导出无法单表恢复快照 + 逻辑导出组合
备份加密密钥存在同一云账号失守即无法解密密钥存密码管理器 + 离线
从不校验备份真恢复时发现文件损坏每次备份计算校验和
从不演练不知道恢复要多久、缺什么每季度一次计时演练
备份保留期太长或太短成本失控或恢复点不足按数据等级设保留策略

十一、行动清单

  • 给数据分级,写出 A 级数据的 RPO/RTO。
  • 列出所有需要备份的对象,填进清单。
  • 确认备份不在生产账号下。
  • 为 A 级数据配置时间点恢复或持续归档。
  • 备份加密,密钥与备份分开存放。
  • 开启对象版本控制或对象锁。
  • 每季度做一次计时恢复演练,记录实际 RTO/RPO。
  • 把恢复手册放在能离线打开的位置。

使用边界与实操验收

S3 Object Lock 依赖版本控制并保护指定版本;R2 的 Bucket Locks 是其自身机制,不能直接当作完整 S3 版本控制替代。OSS 也应按其具体版本控制/WORM/复制文档及账号计划核对;本表没有认证三者功能等价。

先填一张桶级能力表:产品/区域/版本是否开启/保留模式/谁可删除或修改策略/独立账号副本/恢复命令/最近演练。对测试对象验证旧版本或保留对象确实能取回,并确认生产凭据不能删除独立副本。

以下 SQLite 夹具演示“自动恢复后做业务一致性校验”,不是云数据库生产备份方案,也未演示备份加密:

import sqlite3
import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as d:
    d = Path(d)
    with sqlite3.connect(d / "source.db") as db:
        db.execute("CREATE TABLE orders(id INTEGER PRIMARY KEY, cents INTEGER)")
        db.executemany("INSERT INTO orders VALUES(?, ?)", [(1, 1200), (2, 800)])
        db.commit()
        with sqlite3.connect(d / "backup.db") as backup:
            db.backup(backup)
        db.execute("DELETE FROM orders")
        db.commit()
    with sqlite3.connect(d / "backup.db") as backup:
        with sqlite3.connect(d / "restored.db") as restored:
            backup.backup(restored)
            assert restored.execute("PRAGMA integrity_check").fetchone() == ("ok",)
            assert restored.execute("SELECT COUNT(*), SUM(cents) FROM orders").fetchone() == (2, 2000)
    print("PASS: isolated restore, 2 orders, 2000 cents")

真实演练还需核对对象文件与数据库引用、队列、外键、金额、权限和应用启动;行数相同不等于业务完整。

关键参考与核验范围

以下链接支持对应的机制或规则;本文的案例、成本和经营评分是说明方法的假设,不能当作平台统计或收益承诺。核验日期:2026-10-22。

系列导航

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「Indie」更多文章

  1. 定价复盘:价格、转化、留存与退款的一张表
  2. 低流量定价实验:样本不够时怎么测
  3. 结账页:币种、税费、失败重试与拒付