每个月账单邮件来的那天,我的心情都差不多:先扫一眼总额,再安慰自己"下个月应该不会涨了"。这个"下个月"我已经安慰了自己两年。数据量涨上去之后,云存储的账单几乎是一条只升不降的曲线,存储费跟着数据量走,流量费跟着访问走,请求费跟着工具链走。我存的东西大多是冷数据,一年摸不到几次,却一直在为它们付全额的"仓库保管费"。
某天深夜复盘账单的时候,我意识到一件事:我的业务在哪里,和我的数据在哪里,其实是两个问题。业务离开云没法活,数据不是。于是我把"数据回家"当成一个正经课题,花了几周时间把能想到的路线都过了一遍。这篇是我的研究笔记,不是结论宣言。
我把问题拆成了四个方案
先说清楚前提。我的场景是个人加几个小项目的数据:备份、照片库、一部分开发产物的归档,加起来不算巨量,但也不小。访问模式很简单,写入频繁、读取稀少,读取大多来自脚本而不是人。这个前提下,任何方案的出账方式比它的功能列表更值得看。
把需求摊开后,路只有四条:继续用云但想办法省钱;买台 NAS;用闲置硬件自建对象存储;或者云和家各管一半。还有一个背景值得交代:这段时间数据没出过什么大事故,所以我的风险评估偏保守,宁可方案笨一点,也不要半夜起来救火。下面逐个说我的观察。
方案一:留在云上,用分层和生命周期跟账单讨价还价
云厂商自己其实给过台阶。对象存储普遍有存储分层,热层、低频层、归档层,价格逐层递减,再配上生命周期规则,可以让对象在没人访问之后自动沉到更便宜的层。听起来很美,我认真配了一版。
问题是冷数据的特性让省钱空间打折。归档层确实便宜,但取回对象本身要付费,有些还要排队等解冻;一旦访问模式的判断出错,取回的花销可能把省下来的存储费吃回去。低频层相对温和,可对"读得少但不为零"的数据,账单只是涨得慢一点。更麻烦的是,我发现自己根本说不清哪些数据三个月没被碰过,全靠规则去猜,猜错的成本要过几个月才会在账单上显形。
另一个容易低估的项是请求费和流量费。对象存储的计费维度不止存储一个,读写请求按次算,出站流量按 GB 计费,量大了之后这两项很容易追上存储那一项,各家费率不同,方向却大体如此。归档分层省的是存储那一项,取回的动作反而会推高流量和请求两项。分层策略到底省不省钱,得把这几项都摆到桌面上才算得清。
这条路没有错,适合对自家访问模式心里有数的人,我不属于那一类。它的好处也真实:不用迁移,不用运维,数据的可移植性完全不受影响,随时还能反悔。
方案二:NAS 盒子,花钱买省心
NAS 是"数据回家"的成品形态。群晖、威联通这类盒子把 RAID、快照、相册、同步服务都打包好了,插电、建存储池、装套件,一个周末就能用起来。对不想碰命令行的人来说,这是摩擦最小的回家方式。
我看中的点是它的确定性:一次硬件投入,之后每月账单归零,电费除外。但我盘算下来有三个犹豫。一是机器本身不便宜,几盘位的型号加上硬盘,投入不小,而它的硬件性能是为文件服务设计的,不是我想要的存储系统。二是它对外的接口大多是 SMB、NFS 或者厂商自己的同步服务,我的备份脚本走的是 S3 接口,换过去等于把工具链重写一遍,这笔隐形成本账单上看不见。成品 NAS 原生的优先接口确实还是 SMB/NFS,少数系统支持加装 S3 网关(比如 TrueNAS 上跑个 MinIO),但那属于额外折腾,和买盒子图省心的初衷是拧着的。三是容灾靠自己,机器和盘都在家里,断电、被盗、进水,数据跟着一起走,除非再配一台异地。
NAS 适合"文件加家人照片"形态的回家,和我的 S3 依赖有点错位。顺带一提,配置好的快照和异地同步套件确实好用,但整套生态是围绕厂商硬件搭的,哪天想绕开它直接拿数据,还得用它的导出工具走一遍。
方案三:闲置硬件加对象存储,S3 接口是底线
第三条路是用闲置硬件跑一套对象存储,把 S3 接口留在原地。逻辑很简单:我的工具链全都说 S3 方言,那只要家里有个会讲 S3 的服务,迁移就只是换个 endpoint。前提是脚本没踩在兼容矩阵的排除项上:ACL 授权官方明确排除,桶访问日志、桶所有权控制还在计划支持里,依赖这几样的脚本就不是改 endpoint 能救的。
选软件的时候我看了几个选项,最后花时间最多的是 RustFS。它是一个用 Rust 写的分布式对象存储,Apache 2.0 许可证,文档定位写明"面向兼容 S3 的工作负载"。官方还有一份 S3 兼容性矩阵,原话是"实现了经过测试的 Amazon S3 API 子集",哪些操作验证过、哪些还没实现、哪些明确排除,都列在明面上。对我这种要把数据搬回家、又不想回头修脚本的人来说,这种承认自己不是百分之百兼容的诚实,反而比含糊地宣称"完全兼容"更让我放心。
部署比我想的简单。Docker 方式在官方文档里就三步,拉镜像、建卷、跑容器:
docker pull rustfs/rustfs:latest docker volume create rustfs-data docker run -d \ --name rustfs \ --restart unless-stopped \ -p 9000:9000 \ -p 9001:9001 \ -v rustfs-data:/data \ -e RUSTFS_ACCESS_KEY="<your-access-key>" \ -e RUSTFS_SECRET_KEY="<your-secret-key>" \ -e RUSTFS_ADDRESS=":9000" \ -e RUSTFS_CONSOLE_ADDRESS=":9001" \ -e RUSTFS_CONSOLE_ENABLE=true \ -e RUSTFS_OBS_LOGGER_LEVEL=error \ -e RUSTFS_OBS_LOG_DIRECTORY="/var/log/rustfs/" \ rustfs/rustfs:latest \ /data9000 端口是 S3 API,9001 是控制台。文档反复强调一件事:把服务接入网络之前,先把凭证换成自己的唯一值,不要沿用初始的那组默认凭证。这一点我照做了,暴露在家宽上的存储服务,凭证就是大门。
硬件这一环我用的是一台吃灰的小主机加两块机械盘。选它不是因为它强,而是因为它闲着:功耗低,噪音小,塞在角落里没有任何存在感。功耗低在这笔账里也是正经的一项:这样一台小主机常年通电,按 20 瓦上下估算,一年电费百元上下,这才是自建方案真正的"月租";至于把数据搬回家那一次,云端的出站流量费往往比一年电费还显眼,好在它是一次性的。对冷备份场景来说,瓶颈从来不是 CPU,是网线和盘。
数据搬家比想象中顺。RustFS 官方提供了 rclone 集成指南,配置里 provider 选 Other,再把端点指向家里的地址。RustFS 默认就走路径式寻址,rclone 这边保持默认配置就能对上:
[rustfs] type = s3 provider = Other access_key_id = <your-access-key> secret_access_key = <your-secret-key> endpoint = http://localhost:9000 region = us-east-1然后建桶、拷对象:
rclone mkdir rustfs:my-bucket rclone copy /path/to/hello.txt rustfs:my-bucket真实迁移就是把这两条命令里的桶名换掉,一个桶一个桶地做对象拷贝。带宽决定搬运时长,这一点没有魔法。顺带说一句,rclone 的拷贝是增量的,跑完第一轮全量之后,后续维护只是定期补差量。
当然,这条路的代价要说清楚。自建意味着可用性兜底的人从云厂商变成我:磁盘坏了怎么办、机器重启之后服务没起来谁发现,这些以前不存在的问题现在都在我的待办里。官方文档给的路线图也很实在:单机单盘模式没有容错能力,要靠备份兜底;生产环境要多节点多磁盘,用纠删码扛磁盘和节点级别的故障;正式上线前还有一份覆盖硬件、网络、软件和安全的检查清单要过。我这点数据量,接受单机加异地备份的取舍,再往上就得掂量了。
方案四:热数据在云上,冷数据回家
最后一条路是把前面几条组合起来:热数据留在云上,冷数据回家。写入仍在云上完成,到了设定的时间,就把不再访问的对象同步回家里的存储,云上只留最近的东西。
这是纸面上最优的形态。云的弹性保住了,冷数据的成本沉到了家里的电费线上,两边各取所需。冷热的判定我借用了云上的访问记录,哪些前缀半年没被读过,记录里一目了然,比生命周期规则那种自动沉浮多一层人工确认。但它也是四个方案里运维负担最重的一个:要维护两套存储、一条可靠的同步链路,还要保证规则足够聪明,别把下个月可能还要读的对象搬走了。对象万一要重新上云,入站出站的流量两头都要算账,这笔钱平时躲在选择方案的兴奋感后面。
它适合数据量已经大到单边方案都难受、并且有精力长期维护同步链路的人。
研究做完,我的取舍是这样的
一部分确凿的冷数据已经用方案三搬回了家,跑在闲置的小主机上,rclone 定期从云端的归档桶往下同步;活跃的部分留在云上不动。NAS 我没有买,S3 接口这个底线挡住了它。方案一里的生命周期规则我依然在用,只是角色变了,管的是云上剩下那一小部分。
回头看,这次研究里对我决策影响最大的一个判断是:数据可移植性值钱。S3 接口让我的工具链在云和自建之间自由移动,搬家成本从"重写一遍"降到"改一行 endpoint"。也是因为这样,我评估存储方案的标准从功能列表换成了三个问题:数据搬进来容易吗,搬出去容易吗,出问题的时候兜底的是谁。
云存储没有错,它收的是弹性和省心的钱。只是当数据长到一定程度,这份省心的账单会超过很多人愿意为兜底付出的耐心。数据回家的路不止一条,重要的不是选哪条,而是想清楚自己最不愿意为什么东西买单。