一、备份到底备份什么:先把活数据盘成一张清单

所谓VPS备份方案,本质是一份可验证的数据清单加上一个能重复执行的复制流程。绝大多数翻车案例的根因都不是技术不行,而是没人说清楚这台机器上哪些文件丢了就真的完了。典型事故里,业务方最痛的不是数据库表少了几条记录,而是上传的图片、生成的证书、迁移过一次的配置文件全都找不回来了,站点表面还活着,核心功能却已经残废。
延伸对照:cloudcone 的公开机型与线路说明,可结合本节的判断标准自行核对。
一台机器上的活数据大致分三类。第一类是业务数据,主要是数据库文件与数据库逻辑导出,MySQL 的 ibdata1 与各库目录、PostgreSQL 的 base 目录、以及 Redis 的 RDB 文件都算在内。第二类是文件数据,包括站点源码、上传目录、图片附件、视频切片、用户导出文件,以及面板、对象存储挂载点。第三类是环境数据,包括 SSH 密钥与 authorized_keys、crontab 与 systemd 定时器、防火墙规则、Nginx 与数据库的配置文件、Docker 的 compose 文件与数据卷映射。这一类最容易漏,因为它们体积极小但复现代价极高。
盘点这一步要靠命令而不是靠记忆。先看磁盘占用分布,定位真正的数据目录:
# 列出各一级目录占用,找出数据集中区
sudo du -h --max-depth=1 / 2>/dev/null | sort -hr | head -15
# 找出最近 30 天内被修改过的大文件,这些多半是正在增长的数据
sudo find / -xdev -type f -mtime -30 -size +50M \
-exec ls -lh {} \; 2>/dev/null | head -20
# 确认挂载点与物理盘对应关系,避免备份写进了同一块盘
df -hT && lsblk -f
判读方式:正常一台跑业务的 VPS,数据应该集中在两三个目录里,其余系统目录加起来占不到三成。如果 du 显示某个目录突然翻倍,先查是不是日志或缓存没做轮转,那不是备份问题而是磁盘增长问题。把这份清单写进文档并标注每个目录的日增长量,后面所有的备份频率与存储成本都从这个数字推导。
二、快照不等于备份:机房级快照有四个盲区

把「快照」当成备份,是新手最常犯的错。快照是在同一台物理存储上记录的一个时间点镜像,它解决的是「主机磁盘损坏或误操作后快速回滚」的问题,但它的四个盲区决定了它不能独立承担备份职责。
延伸对照:digitalocean 的公开机型与线路说明,可结合本节的判断标准自行核对。
第一个盲区是故障域重合。快照和源盘在同一台宿主机的同一套存储池里,机房级故障、宿主机磁盘阵列降级、存储层的误操作会同时影响源盘和快照。快照是「更快的回滚」,不是「更远的后路」。第二个盲区是逻辑破坏会被一并继承。生产数据库已经错了三天的数据,快照忠实地复制了错误状态,拿它恢复只会把错误放大。
第三个盲区是勒索软件无差别加密。如果攻击者拿到 root 权限并遍历了文件系统,磁盘快照、挂载的备份盘、甚至对象存储的同步目录,只要在加密时间窗之后生成的副本,全都会被加密。快照没有「未被污染」这个属性。第四个盲区是快照只代表过去。回滚到三天前的快照,意味着这三天的业务数据全部丢失,你只是在用确定的损失换掉可能的损失。
所以问自己三个问题就能判断快照够不够:源盘坏了能救回来吗,答案是能;源盘上的数据被误删了能救回来吗,答案是能,但会丢快照之后的数据;整个机房都不在了能救回来吗,答案是不能。三个问题里只要有一个答不上来,就说明你还缺异地层。
还要注意快照的保留策略本身也是一道题。保留窗口太短,比如只留最近三天的快照,那么一次误操作在三天后才发现时就无路可走;保留窗口太长,快照会占满磁盘,而且查找变得困难。常用的折中是保留策略按时间分层:最近 24 小时每小时的快照,中间 7 天每天一份,再往前每周末一份保留四周。这种分层在多数面板里能直接配置,不需要自己写脚本。
另一个容易被忽略的点是快照的创建方式。手动点击创建快照和定时自动快照之间有一个本质差别:手动快照的时间点由你决定,而自动快照的时间点由面板决定,后者可能恰好落在业务写入最密集的时刻,导致快照文件里的数据库状态恰好处于压力最大的那一刻。手动创建快照前,先把数据库刷出内存缓冲、暂停高频写入任务,会得到干净得多的副本。
在动手写脚本之前,先把手上这台机器的存储能力摸清楚。下面两条命令能直接读出磁盘类型和挂载关系,这决定了能不能用写时复制做快照:
# 查看底层块设备类型,SSD/NVMe 的快照通常秒级完成
lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,ROTA
# 检查根文件系统类型,ext4 的写时复制快照最省空间
findmnt -no FSTYPE /
判读方式:ROTA 列为 1 表示是机械盘,机械盘快照通常是全量复制,耗时随容量线性增长;findmnt 返回 btrfs 或 xfs 时可以做写时复制,快照只记录增量,时间成本与数据变化量成正比而不是与总量成正比。做方案时把这几个数字写进去,别用「快照很快」这种没有口径的描述。
三、三层备份结构:本地、异地与离线冷备各管什么

一套结构合理的备份方案至少有三层,每层负责一类故障,覆盖面互相不重叠。理解这三层的分工,比记住任何一款工具的用法都重要。
延伸对照:hostwinds 的公开机型与线路说明,可结合本节的判断标准自行核对。
第一层是本地快速恢复层,目的是把分钟级的低级错误挡在业务之外。用户误删了一张表、部署脚本写错了配置、磁盘被日志撑满、有人执行了 rm 它的全量删除命令。这一层的形式是本地快照、ZFS 或 Btrfs 写时复制子卷、或者一条可以随时回滚的增量归档。它的恢复耗时通常是分钟级,存储占用低,允许保留密度高,比如保留最近 14 天的每日快照。它的局限也很明显:它和主机构件放在同一个物理故障域里,机房级事件对它无效。
第二层是异地在线层,目的是防主机彻底不可用、账号被封、机房断电断网这类事件。这一层的形式是把备份文件通过加密通道推送到另一个地理位置的对象存储或第二台 VPS。它的恢复耗时取决于传输速度,一条千兆专线传 100GB 数据大约需要 15 分钟到 25 分钟,实际还要加上对象存储的取回排队时间。
第三层是离线冷备层,目的是防逻辑破坏与恶意加密。做法是每月把一份完整备份下载到本地硬盘或刻盘,然后断网存放。它的问题是恢复慢,一次全量恢复可能要几天。这层不需要频繁更新,一个月一次就足以覆盖「勒索软件在快照上潜伏三个月才解密」这类极端场景。
经典的三二一原则说的就是同一件事:至少三份数据副本,放在至少两种不同介质上,其中至少一份在异地。落到具体配置上,比如一份工作数据、一份本地快照、一份异地对象存储,每季度再做一次离线冷备。
| 层级 | 典型形式 | 恢复耗时 | 存储成本 | 能防住的故障 |
|---|---|---|---|---|
| 本地快速层 | 写时复制快照、增量归档 | 分钟级 | 低 | 误删、误配置、磁盘坏道 |
| 异地在线层 | 对象存储、异地 VPS 同步 | 十几分钟到数小时 | 中 | 整机故障、机房事件、账号封禁 |
| 离线冷备层 | 本地硬盘、离线介质 | 天级 | 低但占人工 | 勒索软件、逻辑破坏、误删全链路 |
选机器的时候有一个常被忽略的联动关系:备份目标机的磁盘容量必须按「源数据量乘以保留份数」来算,而不是按源数据量算。一台 40GB 系统盘的小机器,装不下 200GB 的历史备份集,这种机器做异地备份节点是没有意义的。
四、RPO 与 RTO:先用两个数字把方案框住

RPO(Recovery Point Objective,恢复点目标)回答的是「最多能容忍丢多少数据」,RTO(Recovery Time Objective,恢复恢复目标)回答的是「最多能容忍停多久」。这两个数字是商业决策,不是技术参数,但它们决定了整个技术方案的成本结构。RPO 十五分钟意味着备份频率至少是每十五分钟一次;RTO 一小时意味着必须有一套能在小时内拉起服务的完整文档与脚本,包括重装系统、恢复数据、重配证书、重启服务四步。
延伸对照:interserver 的公开机型与线路说明,可结合本节的判断标准自行核对。
举一个具体口径。假设一台 4 核 8G 的机器跑着两个电商站,MySQL 库加表约 60GB,日均订单增量约 800MB,日志与附件增量约 1.2GB,合计日增约 2GB。这个量级下几个常见选择的差别非常直观:
| 方案档位 | 备份频率 | 单次全量大小 | RPO 实际值 | 恢复方式与耗时 |
|---|---|---|---|---|
| 每日全量 | 每天一次 | 约 60GB | 最坏约 24 小时 | 恢复全量,数据丢失按当日增量估算 |
| 每日全量加每小时增量 | 全量加 24 次增量 | 增量约 100MB | 最坏约 1 小时 | 恢复全量后追增量,约 30 到 60 分钟 |
| 数据库 binlog 归档 | 实时推送日志 | 增量仅 KB 级 | 最坏约 5 分钟 | 需要先有可用的全量基线 |
按每月 720 小时折算,第二种方案的月度增量传输量约为 24 × 30 × 100MB ≈ 72GB,加上每月一次全量 60GB,月度出网流量约 132GB。这个数字是估算,用于容量规划时要按实际增量波动上浮两成。
算成本的时候别忘了存储也有保底成本。以 60GB 全量加 30 天日增量为口径,异地对象存储大约占用 100GB 到 130GB;如果额外保留 12 个月的历史全量,则总占用会膨胀到 1TB 以上。多出来的成本主要不是存储费,而是存储越久,取回时越可能遇到数据不一致或格式版本过旧的问题。实操上常用的折中是:日增量保留 14 天到 30 天,全量保留 3 到 6 个月,更早的数据只保留一份离线冷备。
RTO 的取值必须和团队能力匹配。一个只有一个人维护的小团队,把 RTO 定成 15 分钟是不现实的,因为夜间告警根本没人响应。诚实的做法是按「最坏情况下有人能处理的时长」来定 RTO,然后把资源投向缩短恢复路径的自动化脚本,而不是投向更频繁的备份。
五、数据库一致性:逻辑备份和物理备份怎么选

数据库是备份里最容易出错的一环,因为「文件复制过去了」不等于「数据是一致的」。逻辑备份导出的是语句或行数据,恢复时由数据库自己重建结构,跨版本兼容性好、可以只恢复单库单表,缺点是恢复慢且恢复期间需要足够内存。物理备份复制的是数据文件块,恢复快,但对版本和实例配置敏感。两者的选择取决于你的恢复目标:如果只是防误删几张表,逻辑备份够用;如果要整机迁移或者缩短恢复时间,物理备份更合适。
延伸对照:itldc 的公开机型与线路说明,可结合本节的判断标准自行核对。
逻辑备份的实用命令有三个坑必须避开。第一个坑是 MyISAM 表不参与事务,--single-transaction 对它无效,备份期间照样会被写坏。第二个坑是 --lock-all-tables 在大库上会长时间阻塞业务。第三个坑是备份完成后没有做完整性验证,文件写了一半也算成功。
# 逻辑备份:--single-transaction 保证 InnoDB 一致性快照,不锁表
mysqldump -u backup -p --single-transaction \
--quick --routines --triggers --events \
--set-gtid-purged=OFF \
--default-character-set=utf8mb4 \
shop > /backup/shop_$(date +%F).sql
# 备份后立刻做压缩与完整性校验,gzip -t 返回 0 才算通过
gzip -9 /backup/shop_$(date +%F).sql && \
gzip -t /backup/shop_$(date +%F).sql.gz && echo "verified ok"
# 恢复演练用的空库导入,导入后核对行数
gunzip -c /backup/shop_$(date +%F).sql.gz | mysql -u backup -p shop_test
判读方式:解压后文件大小与源库 information_schema 里各表 data_length 加 index_length 的总和对比,差值超过 10% 就要排查是不是漏了某个表或者字符集转换出了问题。导入空库后,用 CHECK TABLE 逐表检查,比直接看导入是否报错的可靠得多。
大库要考虑增量方案。MySQL 8.0 可以用 mysqlbinlog 把变更流实时推到异地,再配 Percona XtraBackup 或 Clone 插件做基础快照。实操上更省事的做法是开 binlog 并设置 expire_logs_seconds,这样服务器本地的 binlog 本身就是一份持续滚动的增量备份,配合每日全量构成完整的恢复链。恢复时先导入最近的全量,再按时间戳重放 binlog,能把 RPO 压到分钟级。
PostgreSQL 用户可以直接用 pg_basebackup 做物理备份,用 pg_dump -Fc 做逻辑备份,用 WAL 归档配合 pg_archivecleanup 控制保留窗口。关键前提是 archive_mode = on 且归档目录与数据目录不在同一块盘上,否则归档等于把备份写进被备份的对象里。
备份数据库时还有一个常被忽略的坑:备份脚本跑在数据库机器上,它本身会消耗 CPU 和磁盘 IO。如果备份窗口正好压在业务高峰,会让线上请求变慢,而监控上看不出是备份导致的。经验做法是把备份窗口放在凌晨三点到五点之间,这个时段通常也是夜间批处理最少的时候。如果数据库在多台机器上有主从结构,可以让备份在从库上跑,主库只负责复制,风险更低。
还有一个细节是备份文件的命名与目录规划。文件名里带上日期和时间戳是基本要求,但更重要的是把不同类型的备份分开存放,逻辑导出、文件快照、归档日志各自一个目录,不要混在一起。混在一起的目录在需要恢复时要多花很多时间做筛选判断,而事故当天的每一分钟都很贵。
六、文件增量同步:rsync 的参数与那些反直觉的陷阱

文件备份的核心是用 rsync 配合 --link-dest 做增量快照,它只传输变化的部分,同时保留历史版本。但默认参数下用 rsync 做备份能出各种事故,下面这套参数是踩过坑之后固定下来的写法。
延伸对照:iwebfusion 的公开机型与线路说明,可结合本节的判断标准自行核对。
# 增量快照:--link-dest 指向上一份快照,未变化的文件走硬链接不占额外空间
DATE=$(date +%F)
PREV=$(ls -1d /backup/root/20*-* 2>/dev/null | sort | tail -n 1)
mkdir -p /backup/root/$DATE
rsync -aHAX --numeric-ids --delete \
--link-dest="$PREV" \
/data/www/ /backup/root/$DATE/
# 先用 --dry-run 看一遍将要发生的变更,确认没有误删再实跑
rsync -aHAX --numeric-ids --delete --dry-run \
--link-dest="$PREV" /data/www/ /backup/root/$DATE/
判读方式:先看 --dry-run 的输出,重点检查 deleting 开头的行。如果它要删除大量文件,先确认目标目录挂载是否正确、源路径有没有拼错,以及 --link-dest 指向的上一份快照是否真实存在。--link-dest 路径写错会导致全部文件重传,磁盘瞬间翻倍,这是最常见的资源事故。
三个必须知道的陷阱。第一,--delete 会把目标端多出来的文件删除,如果备份目标就是源目录的子目录,会变成自己删自己。写脚本时把源和目标做成硬校验,路径不合法就退出。第二,保留属主和 ACL 需要 -aHAX 加上 root 权限,普通用户跑会大量报 Operation not permitted,导致权限信息丢失,恢复后站点读不到文件。第三,--link-dest 依赖硬链接,所以源和快照必须在同一个文件系统上,跨盘会静默退化成全量复制。
还要认清一个概念差别:rsync 是镜像同步,不是版本化备份。目标端永远只有一份状态,--link-dest 出来的历史目录必须自己保留。如果只做同步而不保留历史版本,那么同步源被污染的那一天,你没有任何可回退的时点。
除了本地同步,异地同步还有一个更省带宽的做法:先在本地生成压缩包,再把压缩包推过去,而不是让两台机器直接对话。压缩比通常在四比一到十比一之间,尤其是数据库导出和大量小文件,压缩后的传输量会大幅下降。代价是失去了增量传输的好处,需要在压缩前判断文件是否变化。
# 只在内容变化时才重新打包,用校验和而不是时间戳判断
NEW=$(find /data/www -type f -newermt '-1 day' | head -1)
if [ -n "$NEW" ]; then
tar czf /backup/www_$(date +%F).tar.gz -C /data www/
echo "packed $(du -h /backup/www_$(date +%F).tar.gz)"
fi
异地推送用 rsync 走 SSH 时,加上 -z 开启传输压缩、--partial 支持断点续传、--bwlimit 限制带宽避免占满业务出口:
# 限速推送,不影响业务带宽;--partial 断线后可续传
rsync -az --partial --bwlimit=5000 \
--delete /backup/ backup-node:/backup/
判读方式:推送后在对端执行同样的校验和比对,两边文件哈希一致才算成功。压缩传输时不要用 -z 加 -z 的组合(文件本身已压缩又走 rsync 压缩),基本不增加 CPU 收益。
# 检查硬链接是否真的生效:链接数为 1 说明是全量复制,>1 说明复用了旧数据
find /backup/root/$DATE -type f -links +1 | head -5
七、异地存储选型与成本账:别只算存储费

异地层要在三种方案里选:对象存储、另一台 VPS 加同步、或者线下介质。判断标准是三个:取回速度、取回成本、以及你能否在故障当天凌晨三点把它跑起来。
延伸对照:racknerd 的公开机型与线路说明,可结合本节的判断标准自行核对。
对象存储的强项是便宜与可靠,弱项是取回要走外网带宽,按流量计费。假设你的全量备份是 60GB,一次完整恢复需要把它取回本地,按常见的外网取回价折算,一次恢复的直接成本并不低,但和一次停机事故的损失相比几乎可以忽略。真正的坑是恢复频率:每季度做一次恢复演练,一年就是四次取回成本,这笔钱要提前算进预算,否则演练做不下去,方案就名存实亡。
第二台 VPS 的强项是取回速度快,可以直接 rsync 拉回来,也可以直接在那台机器上启动服务接管业务,缩短 RTO。弱项是这台机器自己也要被备份,容易形成备份环。以广告库中有结构化套餐数据的商家为例,justg 公开的 RosCloud 系列按月计价,入门款 2 核 1G、20GB 磁盘、500Mbps 端口、1TB 月流量为每月 19.99 美元,中档 3 核 2G、40GB 磁盘、2TB 流量为每月 29.99 美元,较高配置的 7 核 24G、320GB 磁盘、6TB 流量为每月 69.99 美元。这类产品适合承担备份节点角色,因为备份场景吃的是磁盘容量、端口带宽和流量额度,不吃 CPU。除此之外的商家,例如 CloudCone、DigitalOcean、Hostwinds、Interserver、ITLDC、iWebFusion、RackNerd、Zenlayer、SpinServers 在本站广告库里只有文字简介、没有结构化套餐数据,具体机型与报价请以官网当日报价为准,不要按估算下单。
按每月 720 小时折算做个粗算:假设备份节点需要容纳 200GB 快照、每 24 小时同步一次全量并保留 7 份,本地一份全量 60GB 加 7 份日增量约 700MB,合计约 65GB,配置上 20GB 磁盘的入门款已经不够用,40GB 到 80GB 区间更稳妥。这只是存储与带宽口径的估算,实际还要加上备份节点自身的系统占用与冗余。
| 方案 | 取回速度 | 月成本量级 | 主要隐性成本 |
|---|---|---|---|
| 对象存储 | 受外网带宽限制 | 低 | 取回流量费、跨区请求费 |
| 第二台 VPS 加 rsync | 快,可直接接管业务 | 中 | 备份节点自身的安全与运维 |
| 线下硬盘或离线介质 | 极慢,天级 | 现金成本低 | 人工寄送与保管、可恢复性下降 |
八、加密与密钥:备份更容易被偷看,而不是被删掉

备份文件天然是一个集中存放的敏感副本。它包含全站内容、用户数据、数据库密钥、SSH 私钥,泄露后果往往比宕机更严重,因为它不会自己恢复。加密这件事上最常见的错误是「加密了,但密钥和备份放在同一个地方」,这等于没加密。
延伸对照:zenlayer 的公开机型与线路说明,可结合本节的判断标准自行核对。
目前比较实用的做法是客户端加密,备份端只存密文,密钥只在你的机器上。age 是这几年被广泛采用的选择,密钥管理简单,支持把多个接收者公钥拼接成一个文件,也支持用口令派生密钥。
# 用 age 加密备份,公钥可以公开,私钥必须离线保管
age -R ~/.config/age/recipients.txt -o /backup/shop_$(date +%F).sql.gz.age \
/backup/shop_$(date +%F).sql.gz
# 解密恢复到一个单独的目录,不要覆盖当前正在用的文件
age -d -i ~/.config/age/identity.txt \
-o /tmp/restore/shop_$(date +%F).sql.gz \
/backup/shop_$(date +%F).sql.gz.age
# 校验密文完整性,age 自带认证,失败会直接报错
age -d -i ~/.config/age/identity.txt /backup/shop_$(date +%F).sql.gz.age \
> /dev/null && echo "verified ok"
判读方式:加密后一定要做一次解密回环测试,解密成功并与原文件校验和一致才算通过。校验和的对比方式:
# 加密前记录原始校验和
sha256sum /backup/shop_$(date +%F).sql.gz > /backup/checksums.txt
# 解密后比对,不一致说明链路有问题
sha256sum -c /backup/checksums.txt
密钥保管要给三条硬规则。第一,私钥离线存放,至少打印一份纸质版放在和电脑不同的地方。第二,密钥文件权限设为 600,备份节点上只放公钥。第三,每季度至少验证一次密钥可用性,因为「密钥存在但读不出来」和「密钥丢失」在恢复时是同一个后果。
要不要加密的判断很简单:备份内容包含用户个人信息或凭据,就必须加密;只有纯静态资源且不含任何凭据,可以不加密以节省 CPU。主流 CPU 开启 AES 硬件指令后,加密吞吐通常在 GB/s 量级,对备份窗口的影响可以忽略。
九、自动化流水线:一条命令跑完,并且自带校验

手动执行的备份等于没有备份,因为人在凌晨三点不会想起来跑脚本。但自动化有个致命陷阱:脚本失败时没人知道。带备份脚本跑了两个月、一次都没成功过的案例非常常见,根因通常是密码过期、路径变更、磁盘满、cron 环境变量缺失,而所有这些失败都会静默地把备份文件留在原地,看起来一切正常。
延伸对照:justg 的公开机型与线路说明,可结合本节的判断标准自行核对。
可靠的流水线必须满足三个条件:有日志、有校验、有告警。有日志是把每一步的输出追加到文件;有校验是备份完立刻做完整性检查,不通过就把这次备份标记为失败;有告警是失败时通知到人,不是写进日志就算完。
#!/bin/bash
# /opt/backup/backup.sh —— 备份 + 校验 + 失败告警三合一
set -eo pipefail
STAMP=$(date +%F_%H%M)
DEST=/backup/app
mkdir -p "$DEST"
export PATH=/usr/local/bin:/usr/bin:/bin
mysqldump -u backup -p"$MYSQL_PW" --single-transaction --quick \
--routines --triggers app | gzip -6 > "$DEST/app_$STAMP.sql.gz" \
|| { echo "[$(date -Is)] dump failed" | tee -a /var/log/backup.log; exit 1; }
gzip -t "$DEST/app_$STAMP.sql.gz" \
|| { echo "[$(date -Is)] gzip verify failed" | tee -a /var/log/backup.log; exit 1; }
rsync -aHAX --delete /data/www/ "$DEST/www_$STAMP/" \
|| { echo "[$(date -Is)] rsync failed" | tee -a /var/log/backup.log; exit 1; }
find "$DEST" -maxdepth 1 -mtime +14 -name 'app_*.sql.gz' -delete
find "$DEST" -maxdepth 1 -mtime +14 -type d -name 'www_*' -exec rm -rf {} +
echo "[$(date -Is)] backup ok $STAMP" >> /var/log/backup.log
判读方式:日志里必须出现 backup ok。如果最近一次 backup ok 的时间戳超过一个备份周期,说明任务已经静默失败。判断脚本是否真的在跑,看 crontab 里的环境:
# cron 的 PATH 很短,命令写绝对路径最稳
crontab -l
# 直接执行一次并观察退出码,0 才算成功
bash -x /opt/backup/backup.sh; echo "exit=$?"
还要记住 set -eo pipefail 这三个词,少一个都会让失败被吞掉。pipefail 尤其关键,因为备份命令几乎都带管道,没有它管道前段失败后段依然返回零。
脚本还应该写成幂等的。所谓幂等就是同一条命令跑两次和跑一次的结果一样,不会产生一堆重复文件,也不会因为第二次执行而报错退出。实现方式是用时间戳或者内容哈希作为文件名的一部分,并在执行前检查目标是否已存在。
告警渠道上,最省事的组合是脚本失败时用 curl 推一条消息到监控服务的 Webhook,或者直接发一封邮件到值班邮箱。频率上不要每次都推,只在失败时推,成功只写日志,否则很快就会演变成没人看的噪音。
最后是备份产物本身也需要保护。备份目录的权限要设为 700,备份用户的家目录不要放备份文件,备份脚本本身要防止被篡改。这些不是理论风险,很多入侵事件里,攻击者的第一件事就是改掉备份脚本,让后续备份全部失效。
十、恢复演练:没有演练过的备份等于没有备份

备份的真正验收标准不是文件在不在,而是能不能在规定时间内恢复出可用服务。这一节是整套方案里最容易被跳过、也最不该跳过的部分。行业里有个粗略但有效的经验:真正需要灾难恢复时才发现备份不可用的比例,能占到三成以上,剩下的原因包括密钥丢失、版本不兼容、文档过期、恢复脚本依赖了已经离职同事的环境。
延伸对照:spinservers 的公开机型与线路说明,可结合本节的判断标准自行核对。
恢复演练的标准动作是拿一台全新的机器,用和出事时同样的配置,只允许使用备份和文档,不允许参照记忆或原机器上的残留配置,然后计时完成全过程:重装系统、安装依赖、恢复数据库、恢复文件、重配证书与定时任务、启动服务、跑通一次业务验证。这个过程中出现任何一步卡住,都要写进复盘文档。
# 演练前的基线快照,用来和恢复结果做数据一致性比对
mysqldump -u backup -p --single-transaction app | gzip > /tmp/baseline.sql.gz
# 恢复完成后比对关键表的行数与校验和
mysql -u backup -p -e "SELECT COUNT(*) FROM orders;" app
mysql -u backup -p -e "CHECKSUM TABLE orders;" app
# 恢复完成后清理演练痕迹,避免脏数据混进生产
mysql -u backup -p -e "DROP DATABASE IF EXISTS app_test;"
判读方式:把演练实测耗时和方案里写的 RTO 对比。如果实测是标称的三倍以上,说明文档或流程有问题,优先补自动化脚本而不是放宽 RTO。另外要把恢复过程中每一个需要人工输入的凭据记录下来,尤其是数据库密码、加密私钥、面板令牌,这些是最容易在事故当天想不起来的东西。
演练频率建议每季度一次,重大版本升级后加做一次。每次演练产出一份记录表,包含日期、参与者、实测耗时、遇到的问题、改进项。把这张表累积起来,它比任何一份备份方案文档都更能说明你的真实恢复能力。
演练的时候刻意制造一点不真实感是最有效的训练方法。比如提前把数据库密码改成和文档里不一样的,让恢复者必须自己去密码库里找;或者临时取消某个依赖服务的访问权限,测试恢复流程有没有隐式依赖。这些小障碍能暴露出文档里那些想当然的假设。
还有一个经常被忽略的检查点:恢复出来的系统补丁和依赖版本是否和备份时一致。数据库大版本升级之后,旧备份可能无法在新版本上恢复;反过来也一样。演练时应该用与生产相同的镜像和依赖版本,通过容器或者配置管理工具来锁定,而不是每次手工安装最新的版本。
十一、踩坑复盘:六个真实翻车场景

下面六个场景不是理论推演,都是各类团队实际遇到过的,它们的共同点是当时都以为自己有备份。
延伸对照:gomami 的公开机型与线路说明,可结合本节的判断标准自行核对。
第一个场景,备份脚本静默失败两个月。脚本用 mysqldump -u root -p 明文写在文件里,密码在第三个月被管理员改成带特殊字符的形式,脚本从此每天失败,但 cron 的输出被丢进了没人看的临时目录,磁盘上留着最后一份两个月前的备份。
第二个场景,备份和源数据在同一块盘。运维在一台机器上挂了数据盘做备份,逻辑上确实「备份了」,但机房检修时整台机器被下架,业务直接停摆。
第三个场景,只备份数据库不备份上传目录。数据库完好无损地恢复了,但用户上传的图片和生成的附件全没了,站点的核心功能无法使用,这一次事故的损失远大于数据库本身。
第四个场景,恢复快照后容器数据卷丢失。快照只覆盖了宿主机上的系统盘,而业务数据在 Docker 的外部卷里,宿主机重装时卷没有跟着回来。
第五个场景,备份文件被勒索软件同步加密。攻击者先拿到了备份节点的凭据,再去加密同步目录,异地副本和本地快照一起被污染,这类场景需要离线冷备来兜底。
第六个场景,恢复用错了数据库大版本。备份是用 8.0 导出的,恢复时目标机器装的是 5.7,导入直接失败,凌晨三点在错误的方向上耗掉了两个多小时。
| 症状 | 根因 | 修复动作 |
|---|---|---|
| 日志里长期没有 backup ok | 脚本失败静默、凭据过期 | 加 set -eo pipefail、失败告警、定期巡检日志 |
| 备份文件和解密密钥同时丢失 | 密钥未离线保管 | 密钥打印两份离线存放,季度做回环解密测试 |
| 恢复后站点核心功能不可用 | 只备数据库漏备文件 | 按清单逐项核对,演练时跑业务验证 |
| 快照恢复后数据为空 | 数据在外部卷未被快照覆盖 | 备份清单加入卷挂载点,恢复文档写明卷重建步骤 |
| 异地副本也是加密状态 | 凭据泄露导致同步目录被污染 | 增加离线冷备,恢复前先验证样本可解密 |
十二、决策表与分场景选型路径

把前面的内容收拢成一张决策表,按条件直接选方案。下面这张表按每日新增数据量、停机容忍度和预算三个维度给出组合建议。
延伸对照:isif 的公开机型与线路说明,可结合本节的判断标准自行核对。
| 条件 | 建议组合 | 理由 |
|---|---|---|
| 日增数据小于 1GB,只有一个人维护 | 每日全量加本地快照,异地只做周备份 | 一个人维护时复杂度是最大风险,减少自动化环节数量 |
| 日增数据 1GB 到 10GB,有兼职运维 | 每日全量加每小时增量,异地同步加季度演练 | 需要接近小时的 RPO,增量方案成本可控 |
| 日增数据超过 10GB,有专职团队 | 全量加 binlog 实时归档,双地域对象存储 | 数据量大时全量频率必须下降,靠增量维持 RPO |
| 站点涉及支付或用户隐私 | 全链路加密加离线冷备,密钥离线保管 | 一旦泄露不可挽回,隔离副本是唯一兜底 |
| 预算紧且可容忍一天数据丢失 | 每日全量到异地方案,周期拉到 24 小时 | RPO 放宽一天可以显著减少同步次数与流量 |
| 要求 15 分钟内恢复 | 预置镜像加自动化恢复脚本,每季度演练 | 缩短恢复时间靠脚本和镜像,不靠更频繁的备份 |
分三条路径看更清楚。新手路径是只做每日全量到异地对象存储这一件事,加一份加密,配一条带告警的脚本,三个小时内可以搭完,先保证有,再保证好。预算紧的路径是把备份频率降到每天一次,但把校验做全,宁可每天一次成功并被验证,也不要每小时一次但没人确认。追求稳定的路径是三层全上,加 binlog 实时归档,加每季度恢复演练,并且把演练记录当作运维资产维护。
成本上可以这样粗算:异地对象存储的月成本主要正比于总保留量,按每月 720 小时折算,一个日增 2GB 的站点,保留 30 天日增量加 6 个月全量,大约需要 400GB 到 600GB 存储,月度成本在低价存储区间。第二台 VPS 方案的月成本则正比于磁盘与流量,公开结构化套餐里 40GB 到 80GB 磁盘、2TB 流量量级的入门配置是较合适的起点,具体的机型与报价请以各商家官网当日报价为准。两者叠加的月度成本通常在两位数美元到几十美元区间,这只是量级估算,实际要按你所在区域、目标机房和所选商家核算。
十三、常见问题

备份一个正在运行的数据库,直接复制它的数据目录行不行? 不行,会得到一个不一致的副本。数据库运行时的内存缓冲、事务日志与数据页在磁盘上不是同一时刻的状态,直接复制可能得到撕裂的表。正确做法是用 mysqldump --single-transaction 做逻辑备份,或者用 XtraBackup、pg_basebackup 这类工具做物理备份,并且以工具的官方流程为准。
VPS厂商自带的快照和自动备份功能,能直接当备份方案用吗? 可以作为本地快速恢复层,但不要当完整方案。厂商快照通常和源盘在同一故障域,且只保存当前时点,没有版本历史,也无法应对账号被封禁、账单异常导致的整机下线。至少要再配一份异地副本。
我的站点数据量很小,只有几百兆,有必要搞三层结构吗? 有必要,但可以简化。三层的价值不在于数据量,而在于覆盖不同故障类型。最小的可行版本是两件事:一份加密的异地备份,加上一个每周自动发到你邮箱的校验结果。
备份文件应该保留多久? 保留周期要同时满足合规要求和成本约束。常用的经验是日增量保留 14 到 30 天,全量保留 3 到 6 个月,每季度额外做一份离线冷备。保留超过一年的在线全量意义不大,因为那时恢复它往往要先跨过很多次结构变更,反而不如用较新的备份加上归档日志重建。
异地备份节点应该选哪个地区? 优先选与主站不同的大陆和不同机房,并优先选网络稳定、带宽计费清晰的商家。判断标准是能不能在故障当天把数据拉回来,而不是延迟有多低。同城不同机房在面对区域级断网时的保护作用有限。
备份成功了但恢复时提示版本不兼容怎么办? 这是做备份时没有记录环境导致的。备份包里要同时保存版本清单,包括操作系统版本、数据库版本与关键组件版本,恢复前先按清单准备环境。mysqldump 的输出头部自带版本注释,恢复前可以先读它确认。
怎么确认备份真的能用,而不只是文件在那里? 只能靠定期恢复演练。判断标准是:在全新机器上,仅凭备份文件和文档完成恢复,实测耗时不超过你设定的 RTO,且恢复后能跑通一次真实业务验证。每季度做一次并记录结果,事故时才不会手忙脚乱。
备份文件应该放在哪个目录下比较安全? 放在和源数据不同的物理磁盘或不同的机器上,具体路径取决于你的部署形态。如果是同一台机器的不同分区,只能防误删不能防硬件故障;同一台机器的同一个文件系统里,等于没备份。同一个容器里的不同目录同样无效,容器重建时一起没。
用了 rsync 做同步,还需要另外做全量备份吗? 需要。rsync 只保证源和目标一致,它不保留历史版本;一旦源数据被污染或误删,同步会把错误也同步过去,镜像和源是同步坏的,不是分叉的。全量快照提供的是时间维度上的回退能力,和同步是互补关系,不是替代关系。
备份脚本里要不要加锁,防止两个任务同时跑? 要。加锁能避免上一次还没跑完、下一次又启动导致的文件互相覆盖。加锁的常见做法是创建锁目录而不是锁文件,因为锁文件在进程被强杀后会残留,需要额外清理逻辑。
# 用 mkdir 做原子锁,锁目录已存在就退出,避免并发执行
LOCK=/var/run/backup.lock
if ! mkdir "$LOCK" 2>/dev/null; then
echo "[$(date -Is)] backup already running, skip" >> /var/log/backup.log
exit 0
fi
trap 'rmdir "$LOCK"' EXIT
备份需要加密吗,会不会影响速度? 含用户数据、凭据、密钥的备份必须加密。主流 CPU 开启 AES 硬件指令后,加密吞吐通常在 GB/s 量级,对备份窗口的影响可以忽略。判断依据是备份内容里有没有个人信息或凭据,纯静态资源且不含敏感信息可以例外。
恢复演练应该多久做一次,会不会有风险? 建议每季度一次,重大变更后加做。演练本身没有风险,前提是在独立的环境里做,不要直接拿生产机器做恢复测试。真正有风险的是「在生产机上直接解压备份覆盖」这种做法,务必避免。

喜欢
高兴
鬼脸
呵呵
无聊
伤心