当前备份 NAS 的手段很多,比较流行的是通过利用 OpenList (alist) 暴露夸克网盘 WebDAV,再利用 Depulicati 等,实现加密、增量备份文件。但是 toC 网盘并不稳定,特别是大规模小文件挺同步时,有非常多的错误,同时有些网盘对大文件(4G+)的支持并不够好 [1]。
考虑到我需要备份的都是照片文件,且数据需要保证万无一失,所以打算使用阿里云 OSS 作为远端存储。
价格
这里以阿里云 OSS 为基准,其他云提供商的价格都差不多。
备份 NAS 的场景下,费用主要来源于存储容量成本、GET/PUT 请求次数、取数据流量成本。阿里云 [2] 给出了不同方案的价格。
| 标准型单价 | 低频访问型单价 | 归档型单价 | 冷归档型单价 | 深度冷归档型单价 | |
|---|---|---|---|---|---|
| 存储费用(本地冗余) | 0.12 元/GB/月 | 0.08 元/GB/月 | 0.033 元/GB/月 | 0.015 元/GB/月 | 0.0075 元/GB/月 |
| 内/外网流入流量(数据上传到 OSS) | 0 | 0 | 0 | 0 | 0 |
| 外网流出流量 | 00:00 - 08:00(闲时):0.25 元/GB 08:00 - 24:00(忙时):0.50 元/GB |
00:00 - 08:00(闲时):0.25 元/GB 08:00 - 24:00(忙时):0.50 元/GB |
00:00 - 08:00(闲时):0.25 元/GB 08:00 - 24:00(忙时):0.50 元/GB |
00:00 - 08:00(闲时):0.25 元/GB 08:00 - 24:00(忙时):0.50 元/GB |
00:00 - 08:00(闲时):0.25 元/GB 08:00 - 24:00(忙时):0.50 元/GB |
| 请求费用 PUT | 每月每地域 0 - 500 万次:免费 >500 万次:0.01 元/万次 |
0.1 元/万次 | 0.1 元/万次 | 0.1 元/万次 | 3.5 元/万次 |
| 请求费用 GET | 每月每地域 0 - 2000 万次:免费 >2000 万次:0.01 元/万次 |
0.1 元/万次 | 0.1 元/万次 | 0.1 元/万次 | 0.1 元/万次 |
| 取回费用 | 0 | 0 | 0 | 高优先级:30 元/万次 标准:3 元/万次 批量:0.3 元/万次 |
高优先级:7 元/万次 标准:1.67 元/万次 |
需要说明的是:
- 表格从左往右取数据、查数据越贵,但存储成本越低。
- 本地冗余就足够了,你的数据挂掉同时阿里云的某地机房也挂掉的概率很低。保证安全性的前提下,尽可能的节约成本。
- 请求类型分为了 GET 和 PUT 两种,一般来说,查询文件元数据等操作是 GET 请求,上传等操作是 PUT 请求。
我选择归档型单价,在价格还能接收的情况下,选择最靠左边的。归档型存储本地冗余 500G 空间每年价格在 150 左右,对我来说已经是能够接受的范围了。
除非你需要非常多的容量,否则不推荐深度冷归档型单价,因为 PUT 价格太贵了。
另外使用归档存储时,这些备份工具没办法直接使用。我猜测是这些工具需要保存快照信息到 OSS,每次备份需要先把这些数据拉到本地,而归档存储不能直接数据。所以如果你需要这些工具,请至少使用低频访问型。
工具选型
市面上有很多优秀的备份工具,比如 restic、Kopia、Deplicati [3] 工具,他们不仅提供传输能力,还提供备份能力,比如快照功能、冗余文件优化等。但是我最终选择了 rclone,原因是简单可靠。
正如 [1] 中所说,rclone 有很多问题,但是基于我的场景,这些缺陷反而是可以被忽略的:
- “会造成大量 OSS 请求数,产生额外费用”:经过优化,大部分情况下可以降低到每次 20 次左右的 GET 和 PUT 请求,下文会详细介绍。
- “没有版本历史”:照片备份是一次性的,一般不会有历史版本。
- “没有压缩”:简单保证更强的可靠性,而且存储成本已经不算太高了。
- “没有优化重复文件”:同样的,跟上面一样。
实现和成本优化
技术架构上我使用 systemd timer 作为定时触发器,监控采用 healthchecks.io。
healthchecks.io、systemd timer、rclone 配置随便 google 或者 vibe coding 一下就能搞定,不再赘述。
主要说一下备份脚本,使用方式是 backup {src} {dst} {healthchecks_webhook},如下。
Webhook 在启动前和结束后分别请求一次,这样 healthchecks 可以记录脚本运行时间。
每个月 1 号执行全量备份。Rclone 从远端拉取全部文件 metadata,递归对比后将有修改过的文件(checksum、修改日期等不一致),全量上传到远端。假设现在有 15000 个文件,排除需要上传的 PUT 请求,查询 GET 请求需要至少 15000 次。按照阿里云 OSS 计费标准,差不多需要 0.15 元。
其他日期采用增量备份。第一步在本地列出 36 个小时内被修改过的文件。由于我的脚本每天都会执行,36 个小时基本上能覆盖住一天的变化。--no-traverse 不让 rclone 去递归索引全部文件,而是只针对 36 小时内的新增文件去请求 OSS。特别的,阿里云 OSS 在 GET 请求失败(4xx 代码)时不会计费。第二步则是针对新增的“老文件”。我发现我的照片软件在上传老照片时保留原始拍摄时间,导致无法被 36 小时条件选中。因此第二步拉全量文件的文件名,如果文件名不存在则上传,这种方式只会产生极少量的 PUT 请求。极端情况下老照片被修改过了,那么修改日期时间就会落在 36 小时内,所以基本上不会有遗漏。
增量更新配合每月一次全量更新,就能够确保数据安全的情况下最大程度的降低成本。
function is_first_day_of_month() {
[ "$(date +%d)" = "01" ]
}
function backup() {
local source_path=$1
local remote_path=$2
local webhook_url=$3
local exit_code=0
echo "Copying local($source_path) to remote($remote_path) ..."
curl -sS -m 10 --retry 5 "$webhook_url/start" >/dev/null
if is_first_day_of_month; then
echo "--> Full backup."
rclone copy "$source_path" "$remote_path" \
--fast-list \
--verbose
exit_code=$?
else
echo "--> Recent files modified within 36 hours."
rclone copy "$source_path" "$remote_path" \
--max-age 36h --no-traverse \
--verbose
exit_code=$?
echo "--> Files by name comparison."
rclone copy "$source_path" "$remote_path" \
--fast-list --ignore-existing \
--verbose
exit_code=$((exit_code + $?))
fi
curl -sS -m 10 --retry 5 "$webhook_url/$exit_code" >/dev/null
}
效果也是立竿见影的。即便是没有任何变更,全量备份 15000 个文件的时间需要约 1 分钟。

采用增量备份后只需要 3 秒左右。

从 OSS 监控上来看,采用增量备份后,每天的请求数维持在 20 个左右(4XX GET 请求不计费)。

References
- https://blog.bowen.cool/zh/posts/offsite-disaster-recovery-for-unraid-with-rclone
- https://www.aliyun.com/price/product#/oss/detail/oss
- https://blog.bowen.cool/zh/posts/how-to-encrypt-backup-your-data-on-your-nas