去年有位客户找我,说要给他一台跑 WordPress 的 VPS 配备份,预算有限,别太贵。我先按老套路查了一下数据量:网站文件加数据库 dump,加起来 47G。他一听就乐了,说那每天备份一份,一个月存 30 份就是 1.4T,硬盘直接躺平。
我说不用这么贵,然后把盘做成了 btrfs。这事儿的关键不在于「快照是免费的」这句宣传语,而在于快照到底是怎么实现的btrfs 用的是写时复制,新快照并不拷贝数据块,而是和源子卷共享同一批块,等你真的改了哪个文件,才把那个块单独写出来。这就是为什么我给他做完之后,20 份快照加起来只多占了不到 3% 的空间。
如果你正在给服务器配备份,下面两家在存储硬件上的定位和这篇内容比较贴,可以先拿它们的配置单做参考:搬瓦工(RAID-10 SAS 硬盘阵列的低价 VPS,磁盘冗余做在底层,适合放快照多的备份盘)、EthernetServers(NVMe SSD 固态硬盘主机,快照恢复时读写快,机器位便宜)

btrfs 里最容易搞混的是「卷(volume)」「子卷(subvolume)」「快照(snapshot)」这三个词。卷是整个文件系统,磁盘格式化成一个卷;子卷是卷里一个独立的、可单独挂载的命名空间,根目录本身就是一个子卷;快照则是某个子卷在某一时刻的只读副本,它在 btrfs 内部其实也是一个子卷,只是被标记成只读。
所以关键动作就两条:btrfs subvolume create 开一个新的子卷,btrfs subvolume snapshot 把某个子卷拍成只读快照。两者之间共享文件数据块,共享的部分是只读的任何一方写了,btrfs 才把那个块复制出来给写入方,这就是写时复制。
这里面有个反直觉的地方:写时复制带来的不是「快照更便宜」,而是「快照的空间占用取决于改动量,不取决于数据总量」。100G 的站点你天天改,20 份快照可能吃掉几 G;同一个站点一个月不动,20 份快照几乎不占额外空间。反过来,如果你把快照和源卷混着放,系统还没来得及共享块就先把空间吃光了这也是后面要讲的坑之一。

先规划目录。别把整台机器的根目录当快照对象,那样系统日志、缓存、临时文件全被卷进来,噪音很大。我一般在盘上单独开一个子卷放站点数据,另一个放数据库导出:
sudo btrfs subvolume create /data/www
sudo btrfs subvolume create /data/db
sudo btrfs subvolume snapshot -r /data/www /data/snap/www_$(date +%F)
第三条里的 -r 一定要加,意思是递归地把子卷下嵌套的子卷也一起拍进去,并且生成的快照是只读的(read-only)。如果你忘了加 -r,嵌套子卷不会被包含,备份就是残的这一点我见过太多人踩。
拍完之后看一眼真实占用:
sudo btrfs filesystem usage /data
sudo btrfs filesystem du -s /data/snap/www_2026-11-01
第一个命令看整个文件系统的已用、已分配空间,第二个看某份快照「独占」多少空间。这个独占值才是你真正为这份快照付出的钱。如果一个快照独占 0,那就说明它一行数据都没改过。

有了快照,回滚反而是三步的事。关键是先挂一份快照出来确认内容对不对,别上来就覆盖:
sudo btrfs subvolume snapshot /data/snap/www_2026-11-01 /data/tmp_check
sudo mount -o ro /dev/sdb2 /mnt/check && ls /mnt/check
sudo mv /data/www /data/www_broken && sudo mv /data/tmp_check /data/www
第一行把只读快照复制成一个可写的临时子卷(快照本身不能直接当数据目录用),第二行挂上去看内容,第三行才是真正的替换。第三行里的 mv 之所以写成两步而不是直接删,是因为老目录里可能还有没提交的东西,先改名保留着,确认新版本跑一天没问题再删。
删快照本身也有一套讲究,btrfs subvolume delete 会报错的情况很多,最常见的就是「目标子卷还有子快照」btrfs 不会自动级联删除,你得先把下层的快照按从新到旧的顺序删掉,或者用 btrfs delete subvolume 加 -R 参数递归。

本地快照只能防误删,防不了磁盘整个没。真要能抗故障,得用 btrfs send 和 btrfs receive 把快照增量同步到另一台机器上:
sudo btrfs send -p /data/snap/www_2026-11-01 | ssh backup@10.0.0.9 'sudo btrfs receive /backup'
-p 是 parent,指明这是一份「上一份快照的后代」,对方机器上必须已经有那份父快照,btrfs 才知道只传差异块。第一次全量传不带 -p,之后每次都带,这是增量传输省流量的关键。我那次给客户搭的时候,100G 的站点第一次全量传了 40 分钟,后面每天的增量只跑 3 分钟不到,其中大部分时间还是花在数据库 dump 上。
顺便提醒一句,btrfs send 走 SSH 的时候别忘了加 -C 开启压缩,在带宽紧张的网络上能省不少事:
sudo btrfs send -p /data/snap/www_2026-11-02 | ssh -C backup@10.0.0.9 'sudo btrfs receive /backup'
第一,快照和源卷混放,空间立刻翻倍。新子卷刚建好、还没来得及共享块的时候,系统得先给子卷分配一份元数据和数据块,这时候两边各自占一份。只要你按上面说的顺序先建子卷、先往里写数据、再拍快照共享才能生效。同理,一份快照刚拍出来就去大量改它,独占空间会迅速往上蹿。
第二,快照不是备份,是「同一个盘上的另一个引用」。磁盘挂了、文件系统损了,这份快照跟着一起没。很多人的第一个反应是「我天天拍快照,稳了」,其实还是得配 send/receive 或者至少定期 rsync 到异机。我见过客户把快照目录和网站放同一块盘上,然后整块盘挂掉,快照一个没剩。
第三,删子卷时空间没回来。如果你用的是 btrfs subvolume delete 而它失败了,空间不会释放;而且删掉快照之后,数据块可能还被别的东西引用着,不一定立刻回收。这时候要跑 btrfs filesystem balance(新版是 balance start)重新分配块,跑完再看 filesystem usage,通常就回来了。
第四,快照会放大写入放大。写时复制意味着改一个 4K 的块也要落一次盘,加上 btrfs 自己的元数据双写,SSD 上的写入量比 ext4 高得多。装机的老手一般会顺手把快照保留策略设成「只留最近 N 份 + 每天一份留 30 天」,而不是无脑攒着。我给客户配的时候就是留 7 个日备 + 4 个周备,磁盘占用一直稳在源数据的 1.3 倍左右。
也不是所有机器都适合。文件数量极多(几百万小文件)的小目录树,btrfs 的元数据压力会明显变大;只跑一个静态站点、每天手动 scp 一份 tar 出去的小机器,上 btrfs 反而是给自己添麻烦。我的判断标准很简单:数据在变、需要多个时间点能回退、并且磁盘 IO 还扛得住,那就上 btrfs 子卷快照;否则老老实实写脚本备份更省心。
最后再强调一次那件最容易被忽略的事:快照解决的是「怎么快速回到过去」,不是「怎么保证东西不丢」。前者 btrfs 做得很好,几秒钟的事;后者必须靠异机复制。只做前者不做后者,等于把全部身家押在一块盘上。