用好了下一代文件系統 Btrfs 這些新特性,從此數據安全樂無憂!

  • 2019 年 10 月 31 日
  • 筆記

對於大部分文件系統來說,在磁碟上創建好文件系統,然後再掛載到系統中去就完事了。但對於 Btrfs 來說,除了在格式化和掛載的時候指定不同的參數外,還支援很多其他的功能。比如:管理多塊硬碟、支援 LVM 和 RAID 等,具體的可以參考它的「官方文檔」或者「Linux 下常見文件系統對比」。

Btrfs 是 Oracle 07 年基於 GPL 協議開源的 Linux 文件系統,其目的是替換傳統的 Ext3、Ext4 系列文件系統。Ext 系列文件系統存在著諸多問題,比如反刪除能力有限等;而 Btrfs 在解決問題同時提供了更加強大的高級特性。

Btrfs 特性

Btrfs 在文件系統級別支援寫時複製 (COW) 機制,並且支援快照 (增量快照)、支援對單個文件快照;同時支援單個超大文件、文件檢查、內建 RAID;支援 B 樹子卷 (組合多個物理卷,多卷支援) 等。具體如下:

Btrfs 核心特性:

  • 多物理卷支援:Btrfs 可有多個物理卷組成 (類似 LVM);支援 RAID 以及聯機 添加、刪除、修改
  • 寫時複製更新機制 (COW):複製、更新、替換指針,而非傳統意義上的覆蓋
  • 支援數據及元數據校驗碼:Checksum 機制
  • 支援創建子卷:Subvolume 機制,同時可多層創建
  • 支援快照:基於 COW 實現快照,並且相對於 LVM 可以實現快照的快照 (增量快照)
  • 支援透明壓縮:後台自動壓縮文件(消耗一定 CPU),對前端程式透明

Btrfs 是 Linux 下大家公認的將會替代 ext4 的下一代文件系統,功能非常強大。本篇不會介紹 Btrfs 的原理,也不會介紹 Btrfs 的所有功能,只是挑了其中的 Subvolume 和 Snapshot 這兩個特性來進行介紹。

本篇所有例子都在 Ubuntu-Server-X86_64 16.04 下執行通過。

準備環境

先創建一個虛擬的硬碟,然後將它格式化成 Btrfs,最後將它掛載到目錄 /mnt/btrfs 。

  # 為了簡單起見,這裡只使用一塊硬碟來做測試(Btrf s可以管理多塊硬碟或分區)。  # 新建一個文件,用來虛擬一塊硬碟。  dev@ubuntu:~$ fallocate -l 512M /tmp/btrfs.img    # 在上面創建 Btrfs 文件系統  dev@ubuntu:~$ mkfs.btrfs /tmp/btrfs.img  btrfs-progs v4.4  See http://btrfs.wiki.kernel.org for more information.    Label:              (null)  UUID:               fd5efcd3-adc2-406b-a684-e6c87dde99a1  Node size:          16384  Sector size:        4096  Filesystem size:    512.00MiB  Block group profiles:    Data:             single            8.00MiB    Metadata:         DUP              40.00MiB    System:           DUP              12.00MiB  SSD detected:       no  Incompat features:  extref, skinny-metadata  Number of devices:  1  Devices:     ID        SIZE  PATH      1   512.00MiB  /tmp/btrfs.img    # 創建文件夾並掛載  dev@ubuntu:~$ sudo mkdir /mnt/btrfs  dev@ubuntu:~$ sudo mount /tmp/btrfs.img /mnt/btrfs    # 修改許可權,這樣後面的部分操作就不再需要 sudo  dev@ubuntu:~$ sudo chmod 777 /mnt/btrfs

Subvolume

可以把 Subvolume 理解為一個虛擬的設備,由 Btrfs 管理,創建好了之後就自動掛載到了 Btrfs 文件系統的一個目錄上,所以我們在文件系統裡面看到的 Subvolume 就是一個目錄,但它是一個特殊的目錄,具有掛載點的一些屬性。

新創建的 Btrfs 文件系統會創建一個路徑為 「/」 的默認 Subvolume,即 Root Subvolume。其 ID 為 5(別名為 0),這是一個 ID 和目錄都預設好的 Subvolume。

  # 這裡從 mount 的參數 「subvolid=5,subvol=/」 就可以看出來默認的 Root Subvolume 的 id 為 5,路徑為 「/」 。  dev@debian:/mnt/btrfs$ mount|grep btrfs  /dev/loop1 on /mnt/btrfs type btrfs (rw,relatime,space_cache,subvolid=5,subvol=/)

創建 Subvolume

這裡我們將會利用 Btrfs 提供的工具創建兩個新 Subvolume 和兩個文件夾,來看看他們之間的差別

dev@ubuntu:~$ cd /mnt/btrfs  # btrfs 命令是 Btrfs 提供的應用層工具,可以用來管理 Btrfs。  # 這裡依次創建兩個 Subvolume,創建完成之後會自動在當前目錄下生成兩個目錄。  dev@ubuntu:/mnt/btrfs$ btrfs subvolume create sub1  Create subvolume './sub1'  dev@ubuntu:/mnt/btrfs$ btrfs subvolume create sub2  Create subvolume './sub2'    # 創建兩個文件夾  dev@ubuntu:/mnt/btrfs$ mkdir dir1 dir2    # 在sub1、sub2 和 dir1 中分別創建一個文件  dev@ubuntu:/mnt/btrfs$ touch dir1/dir1-01.txt  dev@ubuntu:/mnt/btrfs$ touch sub1/sub1-01.txt  dev@ubuntu:/mnt/btrfs$ touch sub2/sub2-01.txt    # 最後看看目錄結構,是不是看起來 sub1 和 dir1 沒什麼區別?  dev@ubuntu:/mnt/btrfs$ tree  .  ├── dir1  │   └── dir1-01.txt  ├── dir2  ├── sub1  │   └── sub1-01.txt  └── sub2      └── sub2-01.txt

不過由於每個 Subvolume 都是一個單獨的虛擬設備,所以無法跨 Subvolume 建立硬鏈接。

# 雖然 sub1 和 sub2 屬於相同的 Btrfs 文件系統,並且在一塊物理硬碟上。但由於他們屬於不同的 Subvolume,所以在它們之間建立硬鏈接失敗。  dev@ubuntu:/mnt/btrfs$ ln ./sub1/sub1-01.txt ./sub2/  ln: failed to create hard link './sub2/sub1-01.txt' => './sub1/sub1-01.txt': Invalid cross-device link

刪除 Subvolume

Subvolume 不能用 rm 命令來刪除的,只能通過 btrfs 命令來刪除。

# 普通的目錄通過 rm 命令就可以被刪除  dev@ubuntu:/mnt/btrfs$ rm -r dir2    # 通過 rm 命令刪除 Subvolume 就會失敗  dev@ubuntu:/mnt/btrfs$ sudo rm -r sub2  rm: cannot remove 'sub2': Operation not permitted    # 需要通過 btrfs 命令才能刪除,刪除 sub2 成功(就算 Subvolume 裡面有文件也能被刪除)  dev@ubuntu:/mnt/btrfs$ sudo btrfs subvolume del sub2  Delete subvolume (no-commit): '/mnt/btrfs/sub2'  dev@ubuntu:/mnt/btrfs$ tree  .  ├── dir1  │   └── dir1-01.txt  └── sub1      └── sub1-01.txt

上面刪除的時候可以看到這樣的提示:Delete subvolume (no-commit),表示 Subvolume 被刪除了,但沒有提交。意思是在記憶體裡面生效了,但磁碟上的內容還沒刪,意味著如果這個時候系統 Crash 掉,這個 Subvolume 有可能還會回來。Btrfs 這樣做的好處是刪除速度很快,不會影響使用,缺點是有可能在後台 Commit 的過程中系統掛掉,導致 Commit 失敗。

為了確保 Subvolume 里的數據被真正的從磁碟上移除掉,可以在刪除 Subvolume 的時候指定 -c 參數,這樣 btrfs命令會等提交完成之後再返回。

dev@ubuntu:/mnt/btrfs$ sudo btrfs subvolume del -c sub2Delete subvolume (commit): '/mnt/btrfs/sub2'

掛載 Subvolume

Subvolume 可以直接通過 mount 命令進行掛載,和掛載其它設備沒什麼區別,具體的掛載參數請查看參考官方文檔。

# 創建一個用於掛載點的目錄dev@ubuntu:/mnt/btrfs$ sudo mkdir /mnt/sub1  # 先查看待掛載的 Subvolume 的 iddev@debian:/mnt/btrfs$ sudo btrfs subvolume list /mnt/btrfs/ID 256 gen 9 top level 5 path sub1  # 通過 -o 參數來指定要掛載的 Subvolume 的 ID# 通過路徑來掛載也是一樣的效果:sudo mount -o subvol=/sub1 /tmp/btrfs.img /mnt/sub1/dev@debian:/mnt/btrfs$ sudo mount -o subvolid=256 /tmp/btrfs.img /mnt/sub1/dev@debian:/mnt/btrfs$ tree /mnt/sub1//mnt/sub1/└── sub1-01.txt

設置 Subvolume 只讀

Subvolume 可以被設置成只讀狀態。

# 通過 btrfs property 可以查看和修改 Subvolume 的只讀狀態# 默認情況下,Subvolume 的只讀屬性為 false,即允許寫dev@ubuntu:/mnt/btrfs$ btrfs property get -ts ./sub1/ro=false  # 將 sub1 的只讀屬性設置成 truedev@ubuntu:/mnt/btrfs$ btrfs property set -ts ./sub1/ ro truedev@ubuntu:/mnt/btrfs$ btrfs property get -ts ./sub1ro=true  # 寫文件失敗,提示文件系統只讀dev@ubuntu:/mnt/btrfs$ touch ./sub1/sub1-02.txttouch: cannot touch './sub1/sub1-02.txt': Read-only file system  # 將sub1的狀態改回去,以免影響後續測試dev@ubuntu:/mnt/btrfs$ btrfs property set -ts ./sub1/ ro false

Snapshot

可以在 Subvolume 的基礎上製作快照,幾點需要注意:

  • 默認情況下 Subvolume 的快照是可寫的
  • 快照是特殊的 Subvolume,具有 Subvolume 的屬性。所以快照也可以通過 mount 掛載,也可以通過 btrfs property 命令設置只讀屬性
  • 由於快照的本質就是一個 Subvolume ,所以可以在快照上面再做快照

在 Subvolume 上做了快照後,Subvolume 和快照就會共享所有的文件。只有當文件更新的時候,才會觸發 COW(copy on write),所以創建快照很快,基本不花時間。並且 Btrfs 的 COW 機制很高效,就算多個快照共享一個文件,更新這個文件也和更新一個普通文件差不多的速度。

如果用過 Git 的話,就能很容易理解 Btrfs 里的快照,可以把 Subvolume 理解為 Git 裡面的 master 分支,而快照就是從 master checkout 出來的新分支,於是快照跟 Git 里的分支有類似的特點:

  • 創建快照幾乎沒有開銷
  • 可以在快照的基礎上再創建快照
  • 當前快照裡面的修改不會影響其它快照
  • 快照可以被刪除

當然 Subvolume 也可以像 Git 里的 master 一樣被刪除。

創建快照

# 在 Root Subvolume 的基礎上創建一個快照# 默認情況下快照是可寫的,如果要創建只讀快照,需要加上 -r 參數dev@debian:/mnt/btrfs$ sudo btrfs subvolume snapshot ./ ./snap-rootCreate a snapshot of './' in './snap-root'  # 創建完成後,可以看到我們已經有了兩個 Subvolumedev@debian:/mnt/btrfs$ sudo btrfs subvolume list ./ID 256 gen 11 top level 5 path sub1ID 257 gen 13 top level 5 path snap-root  # 我們可以通過指定 -s 參數來只列出快照dev@debian:/mnt/btrfs$ sudo btrfs subvolume list -s ./ID 257 gen 10 cgen 10 top level 5 otime 2017-03-05 21:46:03 path snap-root  # 再來看看快照 snap-root 中的文件,可以看到有 dir1 及下面的文件,但看不到 sub1 下的文件,那是因為 sub1 是一個subvolume。在做一個 Subvolume 的快照的時候,不會將它裡面的 Subvolume 也做快照dev@debian:/mnt/btrfs$ tree ./snap-root./snap-root├── dir1│   └── dir1-01.txt└── sub1  # 創建 sub1 的一個快照,可以看到 sub1 裡面的文件出現在了快照裡面dev@debian:/mnt/btrfs$ sudo btrfs subvolume snapshot ./sub1/ ./snap-sub1Create a snapshot of './sub1/' in './snap-sub1'  # 然後在 sub1 和它的快照 snap-sub1 下面各自創建一個文件,會發現它們之間不受影響dev@debian:/mnt/btrfs$ touch snap-sub1/snap-sub1-01.txtdev@debian:/mnt/btrfs$ touch sub1/sub1-02.txtdev@debian:/mnt/btrfs$ tree.├── dir1│   └── dir1-01.txt├── snap-root│   ├── dir1│   │   └── dir1-01.txt│   └── sub1├── snap-sub1│   ├── snap-sub1-01.txt│   └── sub1-01.txt└── sub1    ├── sub1-01.txt    └── sub1-02.txt

刪除快照

刪除快照和刪除 Subvolume 是一樣的,沒有區別。

dev@debian:/mnt/btrfs$ sudo btrfs subvolume del snap-rootDelete subvolume (no-commit): '/mnt/btrfs/snap-root'dev@debian:/mnt/btrfs$ sudo btrfs subvolume del snap-sub1Delete subvolume (no-commit): '/mnt/btrfs/snap-sub1'dev@debian:/mnt/btrfs$ tree.├── dir1│   └── dir1-01.txt└── sub1    ├── sub1-01.txt    └── sub1-02.txt

Default Subvolume

可以設置 Btrfs 分區的默認 Subvolume,即在掛載磁碟的時候,可以只讓分區中的指定 Subvolume 對用戶可見。看下面的例子:

# 查看 sub1 的IDdev@debian:/mnt/btrfs$ sudo btrfs subvolume list ./ID 256 gen 14 top level 5 path sub1  # 將 sub1 設置為當前 Btrfs 文件系統的默認 Subvolumedev@debian:/mnt/btrfs$ sudo btrfs subvolume set-default 256 /mnt/btrfs/  # 重新將虛擬硬碟掛載到一個新目錄dev@debian:/mnt/btrfs$ sudo mkdir /mnt/btrfs1dev@debian:/mnt/btrfs$ sudo mount /tmp/btrfs.img /mnt/btrfs1/  # 這裡將只能看到 sub1 下的文件dev@debian:/mnt/btrfs$ tree /mnt/btrfs1/mnt/btrfs1├── sub1-01.txt└── sub1-02.txt  # 由於 Btrfs 原來的默認 Subvolume 是 Root Subvolume,其 ID 是5(也可以通過 0 來標識),所以我們可以通過同樣的命令將默認 Subvolume 再改回去dev@debian:/mnt/btrfs$ sudo btrfs subvolume set-default 0 /mnt/btrfs/

Default Subvolume 有什麼用呢?

利用 Snapshot 和 Default Subvolume,可以很方便的實現不同系統版本的切換。比如將系統安裝在一個 Subvolume 下面,當要做什麼危險操作的時候,先在 Subvolume 的基礎上做一個快照 A。如果操作成功,那麼什麼都不用做(或者把 A 刪掉),繼續用原來的 Subvolume,A 不被刪掉也沒關係,多一個快照在那裡也不佔空間。如果操作失敗,那麼可以將 A 設置成 Default Subvolume,並將原來的 Subvolume 刪除,這樣就相當於系統回滾。

有了這樣的功能後,Linux 的每次操作都能回滾,養成在修改操作前做 Snapshot 的習慣,就再也不用擔心 rm 誤刪文件了。

現在有些發行版已經有了類似的功能,如 Ubuntu,將安裝工具 Apt 和 Btrfs 結合,自動的在安裝軟體之前打一個 Snapshot。然後安裝軟體,如果成功,刪除新的 Snapshot,如果失敗,修改 Default Subvolume 為新的 Snapshot,刪除掉原來的 Snapshot,這樣對系統沒有任何影響,並且所有操作對用戶是透明的。

隨著 Btrfs 的成熟和普及,相信會改變一些我們使用 Linux 的習慣。

延伸閱讀

btrfs 相關命令

管理 btrfs 使用 btrfs 命令,該命令包含諸多子命令已完成不同的功能管理,常用命令如下:

  • btrfs 文件系統屬性查看:btrfs filesystem show
  • 調整文件系統大小:btrfs filesystem resize +10g MOUNT_POINT
  • 添加硬體設備:btrfs filesystem add DEVICE MOUNT_POINT
  • 均衡文件負載:btrfs blance status|start|pause|resume|cancel MOUNT_POINT
  • 移除物理卷(聯機、自動移動):btrfs device delete DEVICE MOUNT_POINT
  • 動態調整數據存放機制:btrfs balance start -dconvert=RAID MOUNT_POINT
  • 動態調整元數據存放機制:btrfs balance start -mconvert=RAID MOUNT_POINT
  • 動態調整文件系統數據數據存放機制:btrfs balance start -sconvert=RAID MOUNT_POINT
  • 創建子卷:btrfs subvolume create MOUNT_POINT/DIR
  • 列出所有子卷:btrfs subvolume list MOUNT_POINT
  • 顯示子卷詳細資訊:btrfs subvolume show MOUNT_POINT
  • 刪除子卷:btrfs subvolume delete MOUNT_POIN/DIR
  • 創建子卷快照(子卷快照必須存放與當前子卷的同一父卷中):btrfs subvolume snapshot SUBVOL PARVOL
  • 刪除快照同刪除子卷一樣:btrfs subvolume delete MOUNT_POIN/DIR

相關閱讀鏈接

  1. Btrfs 官方文檔:https://btrfs.wiki.kernel.org/index.php/Main_Page
  2. Linux 下常見文件系統對比 :https://segmentfault.com/a/1190000008481493
  3. Btrfs 官方掛載硬碟文檔:https://btrfs.wiki.kernel.org/index.php/Manpage/btrfs(5)#MOUNT_OPTIONS

參考文檔

  1. https://segmentfault.com/a/1190000008605135
  2. https://mritd.me/2017/03/20/btrfs-note/