🐧 第七章 文件系统
📄 一句话概括

100 GB 数据、10 个快照、每个快照后改 1 GB:全量复制要 1100 GB,Btrfs 只要 110 GB——1.1 倍。快照几乎免费,代价是碎片、df 看不懂、满盘难操作。

📝 本节和前面几节的关系

7.6 讲了写时复制的原理,这一节看把它做成产品的样子:Btrfs 把【快照】从备份任务变成了日常操作。

一、Btrfs 是什么

Btrfs 是一个【写时复制】的 Linux 文件系统,它的野心是把【卷管理】与【文件系统】合成一层。 它带来的四件事:快照、子卷、校验和、透明压缩。这四件事都建立在同一个基础上:写时复制。 因为写时复制,任何时刻盘上都有一棵完整的树,所以拍一个快照只需要【复制根指针】;因为每个块只被写一次,所以可以顺手算校验和;因为新块本来就要写,顺手压缩一下也不额外费力。 所以这四件事不是四个功能,是一个特性的四种用法。这也是为什么 Btrfs 的逻辑必须整体理解,只记住【能拍快照】会在遇到 df 迷惑时手足无措。

二、快照为什么是日常操作

算一笔账:100 GB 数据,做 10 个快照,每个快照之后改 1 GB。 全量复制:11 份 × 100 GB = 1100 GB。 Btrfs:100 GB + 10 × 1 GB = 110 GB。只多占 10%。 因为快照之间共享所有没改过的块,只有改过的那部分各存一份。快照个数完全不影响到【没改过的那 90 GB】。 所以快照的用法变了:以前【备份】是要计划、要窗口、要停机的事;现在【升级前先拍一张】只是敲一行命令,几秒钟。 这带来一个很实用的工作方式:snapshot / before-upgrade → 升级 → 有问题就反向拍一张回去。不用恢复备份,不用等拷贝。 但记住 7.7 的那句话:快照不是备份。它在同一块盘上,盘坏了两个一起没。

三、子卷、校验和、压缩

子卷(subvolume):在一个卷里划出多个【独立的树】,各自可以单独拍快照、单独设配额、单独挂载。它不像分区那样固定大小,空间是共享的。 校验和:每个数据块都有 CRC32C(可选更强的算法)。btrfs scrub 能全盘重算一遍,主动发现坏块。配 RAID1 时还能自动从副本修复。代价是每个块多几十字节,以及每次读写都要算一遍。 透明压缩:写的时候压,读的时候解。对文本、日志、代码通常能压到一半以下,代价是 CPU。它还有个意外好处:压缩后写入量变少,在慢盘上反而更快。 这四件事加起来,Btrfs 更像【一个带文件接口的存储管理器】,而不是传统意义上的一张【文件系统】。

四、三个必须知道的坑

① 碎片。写时复制意味着每次改都写到新地方,长期反复改的文件会碎成一片,顺序读退化成随机读。虚拟机镜像、数据库文件是重灾区——这类文件建议关掉 COW(chattr +C)。 ② df 的数字很迷惑。因为块是共享的,df 说【用了 30 GB】,但删掉一个快照可能一个字节都没释放(别的快照还指着)。要看真实占用得用 btrfs filesystem usage /。 ③ 满盘时几乎无法操作。COW 文件系统在接近满时需要空间来做元数据更新,写满之后连删除都可能失败。所以官方建议留 5%~10% 不要用满。这一条最容易在【备份盘写满】的时候咬人。 一句话:Btrfs 把【快照】从【备份功能】变成了【日常操作】,代价是【你得重新学怎么判断磁盘还剩多少】。

五、动手 Demo:Btrfs——把【快照】做成日常操作

这一节算两笔账:10 个快照的空间代价,以及为什么 df 在 Btrfs 上会给出让人误解的数字。

GB = 1024 ** 3

base = 100 * GB      # 基础数据 100 GB
per_change = 1 * GB  # 每个快照后改 1 GB
n = 10               # 10 个快照

full = (n + 1) * base / GB          # 全量复制
cow = (base + n * per_change) / GB  # 写时复制共享

print("全量复制 %.0f GB(%.1fx)" % (full, full / (base / GB)))
print("COW 快照 %.0f GB(%.1fx)" % (cow, cow / (base / GB)))
print("只多占 %.0f%%" % ((cow / (base / GB) - 1) * 100))

实跑输出:

--------------------------------------------------------------------
Btrfs——把【快照】做成日常操作
--------------------------------------------------------------------
① 十个快照,占多少空间

  做法                   要多少空间     相对
  ----------------------------------------------
  每个快照都全量复制     1100 GB        11.0x
  Btrfs 写时复制快照     110 GB         1.1x

  **10 个快照只多占 10%。**
  **因为快照之间【共享所有没改过的块】,只有改过的那部分各存一份。**

② Btrfs 的四件事

  功能         干什么用                     代价
  ---------------------------------------------------------
  快照         一瞬间拍下整个子卷           几乎没有
  子卷         在一个卷里划出多个独立树     没有额外代价
  校验和       每个数据块都有 CRC32C        空间与 CPU
  透明压缩     写时压缩,读时解压           CPU 换空间

  **快照最实用的场景是【升级前先拍一张】。**
  **升级搞坏了,反向再拍一张就回去了**——
  **不用恢复备份,不用等拷贝,几秒钟。**

③ 但 Btrfs 不是免费的午餐

  **① 碎片。**
  **写时复制意味着每次改都写到新地方**,
  **长期反复改的文件会碎成一片,顺序读退化成随机读。**
  **② `df` 的数字很迷惑。**
  **因为块是共享的,`df` 说【用了 30 GB】,**
  **但删掉一个快照可能一个字节都没释放**(别的快照还指着)。
  **③ 满盘时几乎无法操作。**
  **COW 文件系统在接近满时需要空间做元数据更新,写满之后连删除都可能失败。**
  **所以官方建议留 5%~10% 不要用满。**

  **一句话**:**Btrfs 把【快照】从【备份功能】变成了【日常操作】**,
  **代价是【你得重新学怎么判断磁盘还剩多少】。**

  📋 **Linux 上验证**:`btrfs filesystem df /`;
  `btrfs subvolume list /`;
  `btrfs filesystem usage /`(这个比 df 准)。

1100 GB 对 110 GB。10 个快照只多占 10%——因为快照之间共享所有没改过的块。 注意这个模型假设【每次只改 1 GB】。如果每次改动占基础数据的比例很大,COW 就不省了——它省的永远是【没改的那部分】。 真实 Btrfs 还要算元数据与校验和的开销,以及压缩带来的节省,所以实际数字会在这个量级上下浮动。 最容易踩的坑是【用 df 判断 Btrfs 还剩多少】——它算不清共享块,要用 btrfs filesystem usage。

📋 Linux 上验证:btrfs filesystem df /; 📋 btrfs subvolume list /; 📋 btrfs filesystem usage /(这个比 df 准)。

30 秒实验

  1. btrfs filesystem usage / 与 df -h / 各跑一次,看两个数差多少。
  2. btrfs subvolume list /,看你这台机器有几个子卷。
  3. 想一想:为什么虚拟机镜像放 Btrfs 上要 chattr +C 关掉 COW?

常见误区

  1. 【快照就是备份】。它在同一块盘上,盘坏了两个一起没。它是【版本】不是【备份】。
  2. 【df 显示还有空间就能继续写】。Btrfs 的 df 算不清共享块;接近满时它会突然无法写入,要留 5%~10% 的余量。
  3. 【Btrfs 什么场景都比 ext4 好】。高并发写、虚拟机镜像、数据库文件这些场景,COW 带来的碎片是实打实的负担;ext4 在这些场景仍然更稳。
自测三问(点开对答案) 1. 10 个快照为什么只多占 10%? —— **因为快照之间共享所有没改过的块**;**只有改过的那部分各存一份。** 2. 为什么 Btrfs 上 `df` 的数字不可信? —— **因为它算不清【被多个快照共享的块】**——**删掉一个快照可能一个字节都没释放**。**要看真实占用得用 `btrfs filesystem usage`。** 3. 虚拟机镜像为什么要关掉 COW? —— **因为镜像文件会被反复改同一片区域**,**COW 每次都写到新位置,很快碎成一片**,**性能会明显下降。**

相关笔记