2018-02-05
兩年前,我買了一臺(tái) HP Gen8 微型服務(wù)器,其功能之一是作為網(wǎng)絡(luò)存儲(chǔ)。當(dāng)時(shí)它只接了一塊 SSD 作為系統(tǒng)盤和一塊 2 TB HDD 作為存儲(chǔ)盤。隨著存儲(chǔ)文件的增多,我又先后增加了兩塊 4 TB HDD,現(xiàn)在它已經(jīng)接了共計(jì) 10 TB 的存儲(chǔ)空間。我覺得有必要分享一下我用來將這些硬盤的空間合并在一起的工具——mergerfs。
在講工具之前,我有必要先說明一下我目前的存儲(chǔ)方案。
我的 Gen8 沒有直接裝通用操作系統(tǒng),而是先通過 ESXi 實(shí)現(xiàn)了虛擬化,再將存儲(chǔ)盤通過 RDM 的方式完整地映射給其中的一臺(tái)虛擬機(jī)(Arch Linux)。在 Arch Linux 里運(yùn)行了 Samba, NFS, aria2 RPC, Transmission daemon, BorgBackup 等服務(wù),供局域網(wǎng)電腦存取文件、遠(yuǎn)程下載,以及備份。
我在存儲(chǔ)盤里的東西分為兩類:一類是多份備份中的一份(BorgBackup);另一類是從互聯(lián)網(wǎng)上下載的可再生資源。前者本身有冗余,后者丟了不心疼。出于以上考慮,為了硬盤空間利用率的最大化,我并沒有采用 RAID 1 或 RAID 5 之類的冗余存儲(chǔ)的方案,而是采用了 JBOD 方案——Just Bunch of Disks。
不使用 RAID 做冗余還有一個(gè)原因:我希望這些硬盤從 Gen8 上拔下來之后接到別的電腦上我能直接讀取它們。
當(dāng)我的第一塊存儲(chǔ)盤快要裝滿的時(shí)候,我買了第二塊盤,這時(shí)候就面臨了一個(gè)問題:如何把兩塊硬盤的空間合并?考慮到我已經(jīng)在運(yùn)行的那些服務(wù),我自然不想再增加一個(gè)額外的掛載點(diǎn)。我曾考慮過三個(gè)硬盤空間合并方案:
RAID 0。與 RAID 1 或 RAID 5 不同,RAID 0 是對(duì)多塊相同容量的硬盤進(jìn)行平行讀寫,從而提升性能,其額外效果就是硬盤空間也被合并了。但這種方案非常危險(xiǎn):多塊硬盤中的任意一塊掛了,所有的數(shù)據(jù)都將無法讀取。這個(gè)方案不行。
LVM。相比 RAID 0 的原理,LVM 只是將空間連接起來了,而沒有平行讀寫,所以多塊硬盤中的一塊掛了,也只是丟了那一塊的數(shù)據(jù)。但創(chuàng)建過 LVM PV 的硬盤,在別的機(jī)器上讀取起來比較麻煩,所以這個(gè)方案我也不喜歡。
MooseFS。相對(duì)前兩種方案,由于它在 FUSE 層面實(shí)現(xiàn),所以更靈活一些,甚至可以通過網(wǎng)絡(luò),把沒有掛在 Gen8 上的硬盤也納入存儲(chǔ)空間。但這個(gè)方案和 LVM 一樣,協(xié)作性不強(qiáng),硬盤在別的機(jī)器上只能看到一堆數(shù)據(jù)碎塊文件,因此也被我否決了。
以上三種方案還有一個(gè)問題:我需要把硬盤里現(xiàn)有的數(shù)據(jù)全部倒出來再倒進(jìn)去……我需要的是能將文件分散存儲(chǔ)到多塊硬盤,同時(shí)又不改變文件形態(tài)的方案。
早有人遇到過像我一樣的困境,于是他開發(fā)了 mhddfs。在用了它一段時(shí)間之后,我又發(fā)現(xiàn)了一個(gè)更好的實(shí)現(xiàn) mergerfs。兩者的思路類似,但后者比前者功能更豐富、更安全、更穩(wěn)定。本文以后者為例說明。
mergerfs 的思路是用 FUSE 實(shí)現(xiàn)一個(gè)新的文件系統(tǒng),它的下層存儲(chǔ)并不是直接的塊設(shè)備,而是別的已經(jīng)掛載的文件系統(tǒng)。mergerfs 接收到讀寫請(qǐng)求時(shí),它會(huì)根據(jù)約定好的策略,從下層文件系統(tǒng)中讀取文件,或是將數(shù)據(jù)寫入下層文件系統(tǒng)。mergerfs 所呈現(xiàn)的文件系統(tǒng),容量是所有下層文件系統(tǒng)之和,而內(nèi)容則是所有下層文件系統(tǒng)的合并。
引用 mergerfs README 里的 ASCII art:
A + B = C/disk1 /disk2 /merged| | |+-- /dir1 +-- /dir1 +-- /dir1| | | | | || +-- file1 | +-- file2 | +-- file1| | +-- file3 | +-- file2+-- /dir2 | | +-- file3| | +-- /dir3 || +-- file4 | +-- /dir2| +-- file5 | |+-- file6 | +-- file4 | +-- /dir3 | | | +-- file5 | +-- file6
如圖所示,/merged
是 mergerfs 的掛載點(diǎn),其下層兩個(gè)文件系統(tǒng)的掛載點(diǎn)是 /disk1
和 /disk2
。
這樣一個(gè)文件系統(tǒng)完全符合我的需求:讀寫文件時(shí)能獲得合并空間的優(yōu)勢(shì),而當(dāng)硬盤損壞或是想要直接讀取硬盤里的數(shù)據(jù)的時(shí)候又可以單獨(dú)把硬盤拆出來讀取。而且我不用把現(xiàn)有的數(shù)據(jù)倒騰來倒騰去了,無痛遷移!
mergerfs 的作者非常勤奮,每個(gè)版本都會(huì)為 RHEL / CentOS, Fedora, Debian, Ubuntu 不同發(fā)行版的不同版本、不同架構(gòu)組合打包 30 多個(gè) rpm 和 deb 安裝包,其中包括了 ARM 甚至 PowerPC 架構(gòu),方便使用 Raspberry Pi 或是老 Mac 作為網(wǎng)絡(luò)存儲(chǔ)設(shè)備的用戶。Arch Linux 用戶則可以通過 AUR 安裝。
安裝之后通過編輯 /etc/fstab
來掛載 mergerfs。我使用的 fstab 如下:
/dev/sdb1 /media/disk1 ext4 defaults,noauto 0 0/dev/sdc1 /media/disk2 ext4 defaults,noauto 0 0/dev/sdd1 /media/disk3 ext4 defaults,noauto 0 0/media/disk1:/media/disk2:/media/disk3 /media/vdisk fuse.mergerfs defaults,noauto,allow_other,use_ino,minfreespace=100G,ignorepponrename=true 0 0
前三行是三塊存儲(chǔ)盤的普通掛載,第四行是 mergerfs 的條目,它的掛載源是前三塊盤的的掛載點(diǎn),用冒號(hào)分隔。最后一列的參數(shù)說明:
defaults
: 開啟以下 FUSE 參數(shù)以提升性能:atomic_o_trunc, auto_cache, big_writes, default_permissions, splice_move, splice_read, splice_write;
noauto
: 禁止開機(jī)自動(dòng)掛載。意外關(guān)機(jī)重啟之后我可能需要手動(dòng)檢查文件系統(tǒng)后再掛載,所以我不希望它自動(dòng)掛載;
allow_other
: 允許掛載者以外的用戶訪問 FUSE。你可能需要編輯 /etc/fuse.conf
來允許這一選項(xiàng);
use_ino
: 使用 mergerfs 而不是 libfuse 提供的 inode,使硬鏈接的文件 inode 一致;
minfreespace=100G
: 選擇往哪個(gè)下層文件系統(tǒng)寫文件時(shí),跳過剩余空間低于 100G 的文件系統(tǒng);
ignorepponrename=true
: 重命名文件時(shí),不再遵守路徑保留原則,見下一節(jié)詳解。
寫完 fstab 之后就可以讓 mergerfs 跑起來了:
mount /media/disk1 && mount /media/disk2 && mount /media/disk3 && mount /media/vdisk
效果:
Filesystem Size Used Avail Use% Mounted on/dev/sdb1 1.8T 1.7T 179G 91% /media/disk1/dev/sdc1 3.6T 3.4T 215G 95% /media/disk2/dev/sdd1 3.6T 89M 3.4T 1% /media/disk31:2:3 9.0T 5.0T 3.8T 57% /media/vdisk
disk3 是我今天剛裝上的,所以它還是空的。
如果多塊硬盤里同名的目錄或文件,從哪兒讀?往哪兒寫?如果多塊硬盤都有足夠的剩余空間,在哪塊硬盤創(chuàng)建新文件?mergerfs 對(duì) FUSE 的不同操作有著不同的讀寫策略。默認(rèn)的策略是:
action 類別:對(duì)于 chmod, chown 等改變文件或目錄屬性的操作,mergerfs 檢索所有下層文件系統(tǒng),確保所有文件或目錄都得到更改;
search 類別:對(duì)于 open, getattr 等讀取文件或目錄的操作,mergerfs 按掛載源列表的順序檢索下層文件系統(tǒng),返回第一個(gè)找到結(jié)果;
create 類別:對(duì)于 create, mkdir 等創(chuàng)建文件或目錄的操作,mergerfs 優(yōu)先選擇相對(duì)路徑已經(jīng)存在的下層文件系統(tǒng)中剩余空間最大的那個(gè)作為寫入目標(biāo)。
前兩條很好理解,最后一條比較拗口。舉例來說是這樣:
disk1 剩余 100 GiB 空間,有 /dir1
目錄;
disk2 剩余 200 GiB 空間,有 /dir2
目錄;
disk3 剩余 300 GiB 空間,有 /dir3
目錄;
mergerfs 將這三塊硬盤的文件系統(tǒng)合并成一個(gè),可以同時(shí)看到 /dir1, /dir2, /dir3
三個(gè)目錄。
這時(shí)在 mergerfs 對(duì)于上層文件系統(tǒng)寫入一個(gè) 150 GiB 的文件到 /dir2/foo.bin
位置,按照默認(rèn)的策略,mergerfs 會(huì)選擇 disk2 寫入。因?yàn)椋篸isk1 剩余空間不足(小于 minfreespace
或是只讀文件系統(tǒng)也會(huì)被跳過選擇),而雖然 disk3 比 disk2 剩余空間更多,但因?yàn)?disk2 已經(jīng)有 /dir2
目錄了,所以 mergerfs 會(huì)優(yōu)先選擇寫入 disk2 而不是 disk3。
這個(gè)策略的意義在于,當(dāng)下層文件系統(tǒng)的剩余空間差不多時(shí),你的文件不會(huì)被分散開。比如你正在將你的相機(jī)圖片文件夾復(fù)制到 mergerfs 里,一個(gè)文件夾里有 999 張圖片,第一張圖片的落點(diǎn)也將決定接下來 998 張文件的落點(diǎn),而不會(huì)因?yàn)橄聦游募到y(tǒng)剩余空間的交替變化而一會(huì)兒落到這個(gè)文件系統(tǒng),一會(huì)兒落到那個(gè)文件系統(tǒng)。最終下層文件系統(tǒng)會(huì)被平衡地使用,但相同目錄的文件會(huì)盡可能地在同一個(gè)文件系統(tǒng)里,這非常棒。
但這個(gè)策略一直有一個(gè)痛點(diǎn)讓我難受了很久:移動(dòng)文件。比如 2016 年份的文件位于 disk1,而 2017 年份的文件因?yàn)?disk1 已經(jīng)滿了寫到 disk2 來了,在 2018 年的時(shí)候我想把三年的文件都?xì)w到一個(gè)新目錄里。此時(shí) 2016 年的文件可以瞬間完成,2017 年的文件則由于上述策略會(huì)優(yōu)先選擇 disk1,于是就從瞬間完成變成了緩慢的跨盤移動(dòng),當(dāng)這些文件數(shù)量巨大的時(shí)候,已經(jīng)開始的傳輸我又不敢貿(mào)然中止……這樣的坑我在整理文件時(shí)掉過很多次。終于,mergerfs 2.23.0 版本新增了 ignorepponrename
選項(xiàng),使得在重命名文件的時(shí)候,忽略路徑保留規(guī)則,避免了簡(jiǎn)單的文件整理操作變成痛苦的跨盤移動(dòng)的悲劇。
如果 mergerfs 的默認(rèn)讀寫策略不適用于你的應(yīng)用場(chǎng)景,可以通過掛載參數(shù)選用別的策略。
本文地址:https://wzyboy.im/post/1148.html
2018-02-11 讀者 Rmrf99 <r...@protonmail.com> 來信推薦了 ZFS。Rmrf99 在自己的 Ubuntu 工作站上使用 ZFS 作為存儲(chǔ)方案。我沒有用過 Solaris,對(duì) ZFS on Linux 也不是很了解。在 Rmrf99 的推薦下,我在 Ubuntu 虛擬機(jī)中嘗試了 ZFS。ZFS 將 RAID / LVM 中的「卷」的概念與文件系統(tǒng)概念結(jié)合了:使用塊設(shè)備直接創(chuàng)建 ZFS pool,而 pool 的更小單位就直接是文件系統(tǒng)了,調(diào)整大小、快照、緩存、加密、配額等都很方便,不再需要像 LVM 像俄羅斯套娃那樣一層一層地操作。
然而,在我的使用場(chǎng)景下,ZFS 相比 mergerfs 有兩點(diǎn)不適合我的地方:
有時(shí)我需要高速讀取/寫入大量數(shù)據(jù),我目前的做法是直接將一塊存儲(chǔ)盤從 Gen8 上拔下來,使用 SATA-USB 轉(zhuǎn)換器將其連接至計(jì)算機(jī),而 ZFS pool 中的某個(gè)成員不能脫離 pool 單獨(dú)工作,你也不能將一塊已經(jīng)加入 pool 的成員從 pool 中移除(除非拆毀 pool 并重建);
相比 Ubuntu,目前 Arch Linux 對(duì) ZFS on Linux 的支持不夠完善,使用和維護(hù)成本較高,與 ZFS 的 zero administration 理念相違背。
此外,對(duì)于需要跨平臺(tái)協(xié)作的用戶來說,mergerfs 可以將不同文件系統(tǒng)的分區(qū)拼成一個(gè),使 Ext4 與 NTFS 和諧共處,而 ZFS 在可預(yù)見的未來沒有可用的 Windows 支持。
聯(lián)系客服