Minecraft 服务器的三层备份:冷备份、restic 增量快照与家用 NAS 异地副本
这是 mc-ops 系列的第三篇,讲服务器的备份方案:备份怎么做、存在哪里,以及最重要的一点:怎么证明它真的能恢复。
旧方案的问题
旧系统每天凌晨重启时打一个 tar.gz 冷备份,升级前再打一个快照。它简单可靠,可以直接解压,但有几个问题:
- 一天只有一个恢复点。晚上八点出了事,只能回到当天凌晨,玩家一整天的进度都没了。
- 没有去重,越存越大。每份完整备份 3 GB 左右,升级快照只增不减,备份目录涨到了 22 GB。
- 全在同一块盘上。备份和世界存档在同一台机器的同一块硬盘上,硬盘坏了会一起丢。
- 恢复要登录服务器手动操作,而且从来没有演练过。
现在的三层
| 层 | 频率 | 存在哪里 | 防什么 |
|---|---|---|---|
| tar.gz 冷备份 | 每天 05:00 | 服务器本机 | 兜底:不需要任何工具,解压就能用 |
| restic 增量快照 | 每 6 小时 | 服务器本机的 restic 仓库 | 误操作、熊孩子、刷物品 bug、区块损坏 |
| 异地副本 | 每次快照之后 | 家里的小米智能存储(NAS) | 服务器硬盘损坏、系统出问题 |
第一层:每天的冷备份
每天 05:00,mc-ops 在游戏里倒计时通知玩家,然后停服、打包、开服,整个过程两分钟左右。停服状态下打包,世界文件一定是一致的。冷备份按数量保留,太旧的会自动清理。
保留它是因为它不依赖任何工具:万一 restic 仓库坏了,或者需要在一台什么都没装的机器上恢复,tar.gz 解压就能用。
第二层:restic 增量快照
restic 会把文件切块、去重、加密后存进仓库。每个快照在逻辑上都是一份完整的副本,但没变化的数据块只存一份。实际效果:7 个完整快照一共只占 6.7 GB,日常一次快照只新增几十到一百 MB。
不停服也要保证一致。 快照是在服务器运行时拍的。如果拍的过程中 MC 正在写区块文件,备份里的世界可能是"写到一半"的状态。所以每次拍快照前先让 MC 暂停自动保存并把内存里的数据写盘,拍完再恢复:
save-off
save-all flush
(restic backup ...)
save-on
这几条命令都通过 RCON 发送。restic 以低优先级运行(nice / ionice),尽量不影响游戏。
保留策略越近越密:
restic forget --keep-last 4 --keep-daily 14 --keep-weekly 8 --keep-monthly 6 --prune
- 最近 4 个快照:按每 6 小时一个算,覆盖最近一天;
- 再往前每天保留 1 个,保留 14 天;
- 每周 1 个,保留 8 周;
- 每月 1 个,保留半年。
第三层:异地副本,存到家里的 NAS
我家里有一台小米智能存储。它是封装好的 NAS,不能装软件、不能跑 Docker,也没有定时任务。好在它有一个"网络邻居"功能,其实就是 SMB 共享。
restic 本身不支持 SMB,但它可以用 rclone 做后端,而 rclone 支持直接读写 SMB。这样就不需要把共享挂载到本地,也不需要 root 权限:
# 在 rclone 配置里定义一个 smb 远端(密码用 rclone obscure 处理后再写进配置)
# 然后 restic 仓库地址写成:
restic -r rclone:nas:共享名/mc-ops/restic-repo snapshots
每次本地快照完成后,mc-ops 用 restic copy 把新快照复制到 NAS 上的仓库:
export RESTIC_FROM_REPOSITORY=/path/to/local-repo
export RESTIC_FROM_PASSWORD_FILE=...
restic -r rclone:nas:... copy
有几个细节:
- 目标仓库初始化时要复制源仓库的分块参数:
restic init --copy-chunker-params。不加的话,两个仓库切块的方式不一样,复制时去重就失效了,每次都要传大量重复数据。 - 异地复制用单独的一把锁。 第一次全量复制要传 6 GB 多,花了半个小时。它和本地备份、定时重启不共用锁,所以不会挡住 05:00 的重启。
- 复制失败会告警,但不影响本地快照。 将来如果配了多个异地目标,其中一个失败也不会影响其他目标。
- 限速。 复制默认限速,因为家里的上行带宽同时还承载着玩家的游戏流量。
我也考虑过在腾讯云 COS 上再放一份,代码已经支持 S3 协议的目标。不过 NAS 已经能防住硬盘损坏和服务器本身出问题这类最常见的情况,对一台朋友之间玩的服务器来说够用了,所以暂时没开。它防不了整屋断电、失窃这种情况,这一点要心里有数。
小米智能存储的几个坑
- 实际的共享名带一个自动生成的数字后缀。 在 App 里建的共享叫"mc-ops备份",但对外暴露的名字其实是"mc-ops备份-一串数字",按 App 里显示的名字连接会提示找不到。要先用
rclone lsd nas:列出真实的共享名。 - 用 IP,不要用局域网名称。 App 里显示的局域网名称在 Linux 上需要 NetBIOS 或 mDNS 才能解析,一般的服务器上没有,直接用 IP 更可靠。
- 插着网线不代表 IP 固定。 它仍然通过 DHCP 获取地址,最好在路由器里做 IP 和 MAC 绑定。
- 选"账号密码访问",不要选无密码访问。 无密码的共享,局域网里任何设备都能读写,包括各种智能家居设备。
恢复:没演练过的备份不算备份
面板上的"回退"
在面板的备份页选一个快照,点"回退",可以选两种范围:
- 只回退世界:地图、箱子、玩家背包和进度回到那个时间点,Mod 和配置不动;
- 世界 + Mod + 配置:连同
mods/、config/、server.properties、白名单、封禁名单一起回退。
执行流程:
游戏内倒计时 5 分钟 → 停服 → 给当前状态拍一个 pre-restore 快照
→ 从仓库解出目标快照 → 替换文件 → 开服
几个保护措施:
- 先拍 pre-restore 快照:回退之后后悔了,还可以回退到回退前的状态;
- 开服失败自动换回:被替换下来的文件先留着,新文件开服失败就自动换回去;
- 停服前先检查:仓库能不能连上、快照存不存在,不满足就直接报错,不会白白停服;
- 回退期间按钮禁用,也不会和定时重启同时执行。
为什么是先停服、再解快照,而不是先在后台解好、再停服替换?后者停服时间更短,但既然要回退,玩家在这段时间里玩的内容反正都会被覆盖。而且需要回退的时候,往往是世界已经出了问题,这时候让玩家继续玩,可能把问题弄得更糟。早点停服反而更干净。
两次实际演练
找了一个没人在线的上午,在生产服上实际做了两次回退:
| 从本机仓库 | 从 NAS 仓库 | |
|---|---|---|
| 解出 4.3 GB 世界 | 20 秒 | 18.5 分钟 |
| 总停服时间 | 不到 1 分钟 | 约 19 分钟 |
| 结果 | 成功 | 成功 |
两次都成功了,回退前后的世界只差几个自然更新的区块文件。
从 NAS 恢复慢,查下来主要是服务器的网络:它连的是 2.4 GHz Wi-Fi,协商速率只有 50 到 60 Mbit/s 左右,平均只跑到约 4 MB/s。最后一段恢复的都是小文件,每个文件都要在 SMB 上来回请求一次,速度掉到了 1 MB/s 以下。换成千兆有线后,预计能缩短到一两分钟。
这次演练还有几个收获:
- 平时用本机仓库回退,NAS 留给本机硬盘坏了的时候。
restic copy之后快照 ID 会变。 同一个时间点的快照,在本机和 NAS 上的 ID 不一样。我一开始以为是一样的,第一次演练时没切到 NAS 标签,结果用的是本机仓库,看 ID 才发现。- 长任务需要进度。 18 分钟里面板上只有一行"正在解出快照",完全不知道还要多久。后来用
restic restore --json输出的进度,在任务页加了进度条、速度和剩余时间。速度按最近 30 秒计算,这样小文件导致速度突然变慢时,剩余时间也能跟上。
下载备份
面板上也可以直接下载备份:冷备份直接下载 tar.gz,支持断点续传;restic 快照会现场用 restic dump 导出成 zip。下载要经过云主机转发,和玩家共用带宽,所以默认限速 512 KiB/s,同时只能有一个下载。
最容易被忽略的一件事:备份密码
restic 仓库是加密的,没有密码就解不开。我的仓库密码一开始只存在服务器的配置文件里。这意味着:如果服务器硬盘坏了,密码会跟着丢,NAS 上的备份就成了一堆打不开的数据。
所以加密备份的密码一定要另外保存一份,比如放进密码管理器,或者写在纸上收好。这个步骤不难,但很容易被忽略。
小结
- 冷备份简单可靠,保留它作为兜底;
- restic 让一天多次快照的成本变得很低,保留策略越近越密;
- 异地副本不一定要上云,家里的 NAS 就能防住最常见的硬盘损坏;
- 备份一定要实际演练恢复,演练时发现的问题(Wi-Fi 太慢、快照 ID 会变、缺少进度显示)比写代码时想到的多;
- 加密备份的密码要和备份分开存放。
评论