⚠️ 免责声明
本方案带有强烈的传教意图,仅供参考。仅在特定的需求下有效,请勿在不了解原理细节和个人需求的前提下直接照搬。

我的设备上有两张致钛的 1T 硬盘,分别作为系统盘与数据盘,在它们上面都使用了 Btrfs文件系统

系统盘上因为存放了操作系统和软件等与个人数据无关的内容,所以子卷结构更为复杂,让我们先来聊一聊它。

另请参阅

Why Btrfs?

Btrfs 是一种现代的写时复制(COW)Linux 文件系统,致力于实现一些高级功能,同时着重于容错性、修复性以及易于管理性。它于 2007 年诞生,是 Linux 内核的一部分,并仍在积极开发中。

相比于 Ext3, Ext4ntfs 等文件系统,btrfs提供了一系列丰富的现代化功能和特性,且经过多年的开发和优化,已经足够稳定高效。使其几乎成为了当下最适合日常使用的文件系统。

  • 基于 B-Tree 数据结构的元数据管理而非表,且无日志设计
    • 在底层数据结构原理上带来更高的性能和更小的开销。
  • 最大 16EB 的单文件大小和单文件系统大小支持
  • 写时复制(CoW
    • 每次写入硬盘数据时,先将更新数据写入一个新的 block,当新数据写入成功之后,再更新相关的数据结构指向新 block
    • 以此保证文件系统的一致性,避免突然掉电导致的数据损坏。
  • 针对 SSD 的优化
    • 将多次硬盘空间分配请求聚合成一个大小为 2M 的连续的块。大块连续地址的 I/O 能够让固化在 SSD 内部的微代码更好的进行读写优化,从而提高 I/O 性能。
    • CoW 技术从根本上避免了对同一个物理单元的反复写操作。
    • 集成的闲时异步 TRIM/Discard 来释放块以供复用。
  • 数据校验和与自动校错
    • Btrfs 在读取数据时会同时读取相应的 checksum。如果最终从硬盘读取出来的数据和 checksum 不相同,btrfs 会首先尝试读取数据的镜像备份,如果数据没有镜像备份,btrfs 将返回错误。
    • 以此确保了数据的可靠性,使硬件层面错误存储/读取的数据能够被上层软件感知到,进而避免 silent corruption 现象,并在一定程度上自我修正。同时也使硬件损坏导致的部分数据丢失能够提前直接被感知到。
  • 子卷
    • Subvolume 是很优雅的一个概念。即把文件系统的一部分配置为一个完整的子文件系统,称之为 subvolume
    • 采用 subvolume,一个大的文件系统可以被划分为多个子文件系统,这些子文件系统共享底层的设备空间,在需要空间时便从底层设备中分配。这种模型有很多优点,比如可以充分利用硬盘的带宽,可以简化硬盘空间的管理等。
    • 在逻辑上提供了传统分区的使用体验,但在物理上又没有传统分区的诸多限制。如此在使用体验上,我们获得了多个文件系统分区,可以将它们挂载在不同的位置,分配不同的挂载策略,对某个分区单独操作而不会影响整个 btrfs 文件系统。同时它们又都共享着同一份底层设备空间,不用像传统分区一样担心分区的大小和位置是否合理等。
  • 快照与克隆
    • Btrfs 能够对文件系统的子卷完成快照操作,并在需要时轻松将文件系统回滚到快照所在状态,或直接对快照进行读写操作。
    • 借助 CoW 技术,btrfs 的快照仅需要最长几秒时间即可完成,同时由于快照存储的是增量变化以及共享存储 block 的特性,因此快照所占的空间也近乎可以忽略不计。
    • 相比于不具有该特性的文件系统下只能全量备份的方案在空间和时间的代价上都大大减小。
    • 可读写快照甚至可以直接引导启动,当作一个系统某一状态的LiveCD使用。
  • 透明压缩
    • 通常人们认为将数据写入硬盘前进行压缩会占用大量 CPU 计算时间,必然导致文件系统读写效率的降低。但随着技术的发展,CPU 处理速度和硬盘 I/O 速度间的差距不断加大,此时情况发生了改变。假如一个文件不经过压缩的情况下需要 100 次硬盘 I/O 。但花费少量 CPU 时间进行压缩后,只需要 10 次硬盘 I/O 就可以将压缩后的文件写入磁盘。在这种情况下,I/O 效率反而提高了。
    • Btrfs 提供了多种压缩算法供使用,通过在硬盘 I/O 前进行自动压缩/解压,来提高相同理论硬盘空间的实际可使用空间(~10%),同时提高了 I/O 性能,还减少对硬盘的读写来延长寿命。Btrfs 的压缩还具有启发式特性,当对一个文件的若干 block 尝试压缩后发现压缩率不高时,便会放弃对余下部分的继续压缩。
    • 所谓“透明”即这些操作是文件系统的内部行为,对用户使用而言是无感的。
  • 原生多设备支持
    • Btrfs 支持动态添加设备。用户在系统中增加新的硬盘之后,可以使用 btrfs 的命令将该设备添加到文件系统中。同时文件系统空间的多 chunk 设计也为用户提供了灵活的配置可能。
    • 原生 RAID 支持。
  • 高效的元数据内联文件
    • 通过将小文件的数据直接内联到元数据节点上,来避免独立数据块的分配,提高效率。
  • 目录索引、空间预分配、延迟分配、extent、动态 inode 分配……
    • 借助 B-Tree 的结构、extent 和动态 inode 等特性,使 btrfs 整体性能不会随着硬盘容量的增加而降低,具有卓越的扩展性。

结构布局

屏幕截图_20240603_214926

我的系统盘采用扁平化布局,所有子卷(subvolume)均是 ID 为 5 的顶级子卷的子卷(children),再分别挂载在相应的目录下。

屏幕截图_20240603_215742

子卷挂载点额外挂载参数
@/noatime, compress=zstd:1
@home/homenoatime, compress=zstd:1
@root/rootnoatime, compress=zstd:1
@log/var/lognoatime, compress=zstd:1
@cache/var/cachenoatime, compress=zstd:1
@tmp/var/tmpnoatime, compress=zstd:1
@swap/swap
@snapshots/.snapshots

我的子卷划分主要分为四类。

  • 首先是系统数据,也就是@子卷,将其分出用于容纳操作系统和软件本身,也是系统结构的基石,描述了系统本身的状态。
  • 然后是用户数据部分,@home@root,它们分别对应了普通用户root用户的数据目录,将其单独分出能够避免用户数据被不必要的快照和回滚时造成损失。
  • 接下来是运行时数据部分,也就是@log, @cache@tmp,因为是运行时数据的缓存和日志,因此他们也不应该随着系统被快照留存状态,同时将日志独立分卷能够使通过快照维护系统时,还保留着最新的日志文件用于分析,而不至于被回滚掉。
  • 最后便是其他部分,@swap存放着交换文件,@snapshots存放着所有快照,它们都是有着特殊用途的目录,因此从根目录的子卷中分离出来。

相比于模拟它们在文件系统中的结构进行组织的嵌套化布局,所有子卷均直接组织在顶级子卷之下的扁平化布局,使得子卷结构更加扁平直观,可以对不同的子卷采用独立的挂载参数,便于后续维护和快照。

挂载参数

对于除去两个特殊子卷的所有子卷,我们在挂载时都额外添加了 noatime 参数,这能够禁止更新访问记录来减少不必要的写操作以提高性能,在读密集型工作负载的场景下效果更加明显。虽然可能会导致破坏部分依赖于atime工作的程序,但这个选项几乎已经成为了 btrfs 的标配。

对于透明压缩的算法我们使用了目前最快的我们以在LiveCD中首次安装系统时的格式化与分卷操作为例。 据。过高的压缩等级在带来明显性能开销的同时并没有提高多少压缩率,1 级足够。

除了这两个手动指定的参数外,还有应用一些当前 btrfs 版本下的默认参数,这些参数可能随着版本更新导致默认值发生变更。它们分别是:

  • ssd
    • 当 btrfs 识别到设备为固态硬盘时就会默认启用,提供了对 SSD 的特殊分配优化。
  • discard=async
    • 在闲时异步释放硬盘中未使用的区块,也就是TRIM,对于 SSD 的工作非常有帮助。交由文件系统维护也更加稳定,异步的模式使性能开销几乎可以忽略不计。
  • space_cache=v2
    • 采用新版的空间高速缓存,以少量硬盘空间为代价极大提高读取性能。

操作过程

我们以在LiveCD中首次安装系统时的格式化与分卷操作为例。

格式化

1
2
3
DEVICE=/dev/sda2

mkfs.btrfs -L archlinux /dev/${DEVICE}

布置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

DEVICE=/dev/sda2

mount /dev/${DEVICE} /mnt

btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@root
btrfs subvolume create /mnt/@log
btrfs subvolume create /mnt/@cache
btrfs subvolume create /mnt/@tmp
btrfs subvolume create /mnt/@swap
btrfs subvolume create /mnt/@snapshots

umount /mnt

其中@swap子卷为swapfile预留,@snapshots子卷为snapper快照预留。

挂载

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
DEVICE=/dev/sda2

mount -m /dev/${DEVICE} /mnt -o subvol=@,noatime,compress=zstd:1

mount -m /dev/${DEVICE} /mnt/home -o subvol=@home,noatime,compress=zstd:1

mount -m /dev/${DEVICE} /mnt/root -o subvol=@root,noatime,compress=zstd:1

mount -m /dev/${DEVICE} /mnt/var/log -o subvol=@log,noatime,compress=zstd:1

mount -m /dev/${DEVICE} /mnt/var/cache -o subvol=@cache,noatime,compress=zstd:1

mount -m /dev/${DEVICE} /mnt/var/tmp -o subvol=@tmp,noatime,compress=zstd:1

mount -m /dev/${DEVICE} /mnt/swap -o subvol=@swap
1
2
3
chattr +C /mnt/var/log
chattr +C /mnt/var/cache
chattr +C /mnt/var/tmp

对于/var/log, /var/cache/var/tmp三个可能存在大量文件就地更新的目录,我们禁用了其CoW特性。如果有数据库/虚拟机硬盘存在的目录,也建议禁用,以减少硬盘碎片的产生。

可视化管理

1
sudo pacman -S btrfs-assistant

快照与备份

对于系统日常的快照维护上,我选择了 snapper。配合 snap-pacgrub-btrfs,可以在安装软件包前后,以及固定周期时间自动生成快照,并在 grub 菜单中加入启动到 btrfs 快照的选项,在不回滚的情况下启动到特定状态下的系统,既可以当LiveCD使用,也可以用来验证和排查操作的影响。维护利器!再也不怕我系统玩崩了(

首先安装相关软件包

1
paru -S snapper snap-pac grub-btrfs inotify-tools

在我的 btrfs 子卷布局下,与用户数据和运行时数据无关的操作系统和软件都在@子卷下,因此只需对它创建备份配置即可。我们为根目录创建一个snapper配置并命名为root

1
sudo snapper -c root create-config /

snapper 在使用前会自动生成一个/.snapshots子卷并挂载在/.snapshots目录下用于存储快照。但这与我们的子卷布局规范并不匹配,显得有些突兀。因此我们将其删去,并由我们之前分配时预留的@snapshots子卷代替。

1
2
3
4
5
6
sudo btrfs subvolume delete /.snapshots
sudo mkdir /.snapshots

sudo vim /etc/fstab
# 修改fstab,将@snapshots挂载在/.snapshots,注意修改子卷名、子卷id和挂载点
sudo mount -a

接下去对 snapperroot 配置进行一些调整。

1
sudo vim /etc/snapper/configs/root
  • ALLOW_USERSALLOW_GROUPS后加入用户名
    • 允许用户列出和查看快照
  • 修改TIMELINE_MIN_AGETIMELINE_LIMIT_*
    • 设置 snapper 定时快照保留的每小时/每日/每周/每月/每年快照数量,超过最小时长的过期快照将会被自动清除。
  • 修改NUMBER_MIN_AGE, NUMBER_LIMITNUMBER_LIMIT_IMPORTANT
    • 设置数字备份的最小清理时长和保留数量。
    • snap-pac在软件包安装前后进行的快照属于此类。

最后启动相关的服务和定时器即可完成配置

1
2
3
sudo systemctl enable --now snapper-timeline.timer
sudo systemctl enable --now snapper-cleanup.timer
sudo systemctl enable --now grub-btrfsd.service

默认情况下,snapper 创建的快照为只读快照,可以浏览和回滚但可能不能直接引导启动:(

我们可以通过 grub-btrfs 提供的钩子将其挂载为可读写的,这样就能愉快地直接引导启动进行运维了。

1
2
3
nano /etc/mkinitcpio.conf
# 在 MODULES 中添加 btrfs, 在 HOOK 中添加 grub-btrfs-overlayfs
sudo mkinitcpio -P