Linux運(yùn)維學(xué)習(xí) | Linux之父對(duì)文件系統(tǒng)發(fā)飆
Linus 又發(fā)飆了,這一次是 ext4
如果你訂閱了 Linux Kernel 的 maillist,你一定發(fā)現(xiàn)最近 Linus 又爆粗口了,而這次的對(duì)象是 ext4 文件系統(tǒng)。
On Sun, Aug 6, 2017 at 12:27 PM, Theodore Ts'o <tytso@mit.edu> wrote:
>
> A large number of ext4 bug fixes and cleanups for v4.13
A couple of these appear to be neither cleanups nor fixes. And a lot
of them appear to be very recent.
I've pulled this, but if I hear about problems, ext4 is going to be on
my shit-list, and you'd better be a *lot* more careful about pull
requests. Because this is not ok.
Linus
而這已經(jīng)不是 Linus 第一次對(duì) ext4 文件系統(tǒng)表達(dá)不滿了。
盡管 ext4 文件系統(tǒng)已經(jīng)發(fā)布了多年,也被廣泛應(yīng)用于桌面及服務(wù)器,但關(guān)于 ext4 存在可能丟數(shù)據(jù)的 Bug 報(bào)告就一直沒有中斷過。例如在 2012 年的一封郵件中,Theodore Ts'o 報(bào)告了一次嚴(yán)重的 Bug,已經(jīng)影響了部分 Linux 穩(wěn)定版本的內(nèi)核。
如果你持續(xù)關(guān)注文件系統(tǒng)或內(nèi)核技術(shù),你一定注意過這樣一篇文章:Fuzzing filesystem with AFL。Vegard Nossum 和 Quentin Casasnovas 在 2016 年將用戶態(tài)的 Fuzzing 工具 AFL(American Fuzzing Lop)遷移到內(nèi)核態(tài),并針對(duì)文件系統(tǒng)進(jìn)行了測(cè)試。
結(jié)果是相當(dāng)驚人的。Btrfs,作為 SLES(SUSE Linux Enterprise Server)的默認(rèn)文件系統(tǒng),僅在測(cè)試中堅(jiān)持了 5 秒鐘就掛了。而 ext4 堅(jiān)持時(shí)間最長(zhǎng),但也僅有 2 個(gè)小時(shí)而已。
這個(gè)結(jié)果給我們敲響了警鐘,Linux 文件系統(tǒng)并沒有我們想象中的那么穩(wěn)定。而事實(shí)上,在 Fuzz 測(cè)試下堅(jiān)持時(shí)間長(zhǎng)短僅僅體現(xiàn)出文件系統(tǒng)穩(wěn)定性的一部分。數(shù)據(jù)可靠性,才是文件系統(tǒng)中最核心的屬性。然而 Linux 文件系統(tǒng)社區(qū)的開發(fā)者往往都把注意力放在了性能,以及高級(jí)功能的開發(fā)上,而忽略了可靠性。
今天,我們就帶大家回顧一下 Linux 文件系統(tǒng)的黑歷史,希望能夠警醒大家,不要過分相信和依賴文件系統(tǒng)。同時(shí),在使用文件系統(tǒng)構(gòu)建應(yīng)用時(shí),也需要采用正確的“姿勢(shì)”。
POSIX,一個(gè)奇葩的標(biāo)準(zhǔn)
談到 Linux 文件系統(tǒng),不得不提到 POSIX(Portable Operating System Interface),這樣一個(gè)奇葩的標(biāo)準(zhǔn)。而開發(fā)者對(duì)于 POSIX 的抱怨,可謂是罄竹難書。
作為一個(gè)先有實(shí)現(xiàn),后有標(biāo)準(zhǔn)的 POSIX,在文件系統(tǒng)接口上的定義,可謂是相當(dāng)?shù)?ldquo;簡(jiǎn)潔”。尤其當(dāng)系統(tǒng)發(fā)生 crash 后,對(duì)于文件系統(tǒng)應(yīng)有的行為,更是完全空白,這留給了文件系統(tǒng)開發(fā)者足夠大的“想象空間”。也就是說,如果一個(gè) Linux 文件系統(tǒng)在系統(tǒng)發(fā)生崩潰重啟后,整個(gè)文件系統(tǒng)的內(nèi)容都不見了,也是“符合標(biāo)準(zhǔn)”的。
而事實(shí)上,類似的事情確實(shí)發(fā)生過:在 2015 年,ChromeOS 的開發(fā)者曾報(bào)告了一個(gè) ext4 的問題,有可能導(dǎo)致 Chrome 發(fā)生崩潰。而來自 ext4 開發(fā)者的回答是,“Working As Intended”。
在歷史上,不斷有人嘗試給文件系統(tǒng)提供更加嚴(yán)謹(jǐn)?shù)?Consistency(一致性)定義,尤其是 Crash-Consistency(故障后的一致性)。到目前為止,盡管 POSIX 也經(jīng)歷了幾個(gè)版本,但關(guān)于文件系統(tǒng)接口的定義,還是那個(gè)老樣子。而 POSIX 標(biāo)準(zhǔn),也是造成了文件系統(tǒng)各種問題的一個(gè)很重要的因素。關(guān)于各種一致性的定義,我們后面也會(huì)有文章專門進(jìn)行介紹。
文件系統(tǒng)的黑歷史
文件系統(tǒng)一直有著光輝的發(fā)展歷史,也孕育了許多偉大的 Linux 內(nèi)核貢獻(xiàn)者。從最早的 FFS,到經(jīng)典的 ext2/ext3/ext4,再到擁有黑科技的 Btrfs,XFS,BCacheFS 等。
然而軟件開發(fā)的過程,當(dāng)然不是一帆風(fēng)順的。威斯康辛大學(xué)麥迪遜分校的研究者曾在 FAST '13 上發(fā)表過一篇著名的論文《A Study of Linux File System Evolution》。文章對(duì) 8 年中,Linux 社區(qū)與文件系統(tǒng)相關(guān)的 5079 個(gè) Patch 進(jìn)行了統(tǒng)計(jì)和分析。從其數(shù)據(jù)中可以看出,有將近 40% 的文件系統(tǒng)相關(guān)的 Patch 屬于 Bugfix 類型。換句話說,每提交兩個(gè) Patch,就有可能需要一個(gè) Patch 用于 Bugfix。
而文件系統(tǒng)的 Bug 數(shù)量并沒有隨著時(shí)間的推移而逐漸收斂,隨著新功能不斷的加入,Bug 還在持續(xù)不斷的產(chǎn)生。而 Bug 的集中爆發(fā)也往往源于大的功能演進(jìn)。
而從上圖中可以看出,在所有的 Bug 中,有接近 40% 的 Bug 可能導(dǎo)致數(shù)據(jù)損壞,這還是相當(dāng)驚人的。
可以想象,在 Linux 文件系統(tǒng)的代碼庫(kù)中,還隱藏著許多 Bug,在等待著被人們發(fā)現(xiàn)。
哥倫比亞大學(xué)文件系統(tǒng)領(lǐng)域著名的專家 Junfeng Yang,曾經(jīng)在 OSDI '04 上發(fā)表了一篇論文,該論文也是當(dāng)年 OSDI 的最佳論文。在這篇論文中,Junfeng Yang 通過 FiSC,一種針對(duì)文件系統(tǒng)的 Model Checking 工具,對(duì) ext3,JFS,ReiserFS 都進(jìn)行了檢查,結(jié)果共發(fā)現(xiàn)了 32 個(gè) Bug。而不同于 AFL,F(xiàn)iSC 發(fā)現(xiàn)的 Bug 大部分都會(huì)導(dǎo)致數(shù)據(jù)丟失,而不僅僅是程序崩潰。例如文章中指出了一處 ext3 文件系統(tǒng)的 Bug,該 Bug 的觸發(fā)原因是在通過 fsck 進(jìn)行數(shù)據(jù)恢復(fù)時(shí),使用了錯(cuò)誤的寫入順序,在 journal replay 的過程中,journal 中的數(shù)據(jù)還沒有持久化到磁盤上之前,就清理了 journal,如果此時(shí)發(fā)生斷電故障,則導(dǎo)致數(shù)據(jù)永久性丟失。
對(duì)應(yīng)用程序開發(fā)的影響
對(duì)于大部分應(yīng)用程序開發(fā)者來說,并不會(huì)直接使用文件系統(tǒng)。很多程序員都是面向數(shù)據(jù)庫(kù)進(jìn)行編程,他們的數(shù)據(jù)大多是存在數(shù)據(jù)庫(kù)中的。我們經(jīng)常想當(dāng)然的認(rèn)為,數(shù)據(jù)庫(kù)的開發(fā)者理應(yīng)會(huì)理解文件系統(tǒng)可能存在的問題,并繞過文件系統(tǒng)的 Bug,幫助我們解決各種問題。然而這只是一種僥幸心理罷了,由于文件系統(tǒng)過于復(fù)雜,標(biāo)準(zhǔn)不清晰,即使是專業(yè)的數(shù)據(jù)庫(kù)的開發(fā)人員,也往往無法避開文件系統(tǒng)中所有的問題。
以 LevelDB,我們最常用的一種單機(jī) Key-Value Store 舉例。研究人員分別對(duì) LevelDB 的兩個(gè)版本,1.10 和 1.15 進(jìn)行了測(cè)試,分別發(fā)現(xiàn)了 10 個(gè)和 6 個(gè)不同程度的漏洞。其中 1.10 版本有 1 個(gè)漏洞可能導(dǎo)致數(shù)據(jù)丟失,5 個(gè)漏洞導(dǎo)致數(shù)據(jù)庫(kù)無法打開,4 個(gè)漏洞導(dǎo)致數(shù)據(jù)庫(kù)讀寫錯(cuò)誤。而 1.15 版本分別有 2 個(gè)漏洞導(dǎo)致數(shù)據(jù)庫(kù)無法打開,2 個(gè)漏洞導(dǎo)致數(shù)據(jù)庫(kù)讀寫錯(cuò)誤。
這些問題,大部分源自應(yīng)用開發(fā)者對(duì)文件系統(tǒng)錯(cuò)誤的假設(shè)。也就是說,他們以為文件系統(tǒng)可以保證的特性,而事實(shí)上并不能得到保證。而這些特性,也都是 POSIX 標(biāo)準(zhǔn)中未曾明確定義的。
這里我們舉個(gè)例子:
Append atomicity,追加寫原子性。
向文件中追加寫入,并不意味著是原子性的。如前文 ChromeOS 開發(fā)者遇到的 ext4 的問題,其根本原因,就是假設(shè) ext4 文件系統(tǒng)是保證追加寫原子性的。在這封郵件中,開發(fā)者提供了一個(gè)可以復(fù)現(xiàn)問題的步驟。假設(shè)文件中已經(jīng)有 2522 字節(jié)的數(shù)據(jù),再追加寫入 2500 字節(jié)的數(shù)據(jù),文件大小本應(yīng)為 5022 字節(jié)。而如果在追加寫的過程中,遇到系統(tǒng)崩潰,在系統(tǒng)恢復(fù)后,文件的大小可能是 4096 字節(jié),而非 5022 字節(jié),而文件的內(nèi)容,也可能是垃圾數(shù)據(jù),無法被程序正確識(shí)別。
LevelDB 同樣也假設(shè)了文件系統(tǒng)具有追加寫的原子性,前面提到的一些漏洞就源于此。
而這僅僅是冰山一角。單單關(guān)于文件系統(tǒng)寫入數(shù)據(jù)的原子性,就有包括:?jiǎn)?sector 覆蓋寫,單 sector 追加寫,單 block 覆蓋寫,單 block 追加寫,多 block 追加寫等等。而對(duì)于不同類型的文件系統(tǒng),甚至同一個(gè)文件系統(tǒng)的使用不同參數(shù),對(duì)于原子性都可能具有不同范圍的支持。再考慮到 POSIX 提供的其他接口,包括 creat,rename,unlink,truncate 等等(關(guān)于這些接口背后的坑,我們后續(xù)將單獨(dú)寫文章介紹)。這使得開發(fā)應(yīng)用系統(tǒng),尤其是數(shù)據(jù)庫(kù)系統(tǒng),變得非常復(fù)雜。
開發(fā)者的正確姿勢(shì)是什么
這里我們提供一些建議,希望能夠幫助大家盡量少的踩坑。
首先,對(duì)于大部分應(yīng)用程序員來說,應(yīng)盡可能選擇使用成熟的數(shù)據(jù)庫(kù),而非直接操作文件。盡管如前文所說,在復(fù)雜的文件系統(tǒng)面前,數(shù)據(jù)庫(kù)也無法幸免于難,但數(shù)據(jù)庫(kù)開發(fā)者掌握的關(guān)于文件系統(tǒng)的知識(shí),還是遠(yuǎn)遠(yuǎn)強(qiáng)于普通開發(fā)者的。數(shù)據(jù)庫(kù)也通常提供了數(shù)據(jù)恢復(fù)工具,以及備份工具。這避免了開發(fā)者重新造輪子,也極大的減輕了災(zāi)難發(fā)生后可能帶來的影響。
而對(duì)于單機(jī)數(shù)據(jù)庫(kù),分布式數(shù)據(jù)庫(kù),以及分布式存儲(chǔ)的開發(fā)者來說,我們的建議是盡量避免直接使用文件系統(tǒng),盡可能多的直接使用裸設(shè)備,這避免了很多可能引起問題的接口,例如 creat,rename,truncate 等。例如 SmartX 在設(shè)計(jì)和實(shí)現(xiàn)分布式存儲(chǔ)時(shí),就直接使用裸設(shè)備。
如果必須要使用文件系統(tǒng),也要使用盡量簡(jiǎn)單的 IO 模型,避免多線程,異步的操作。同時(shí),一定要在設(shè)計(jì)的過程中,把對(duì)于文件系統(tǒng)操作的模型抽象出來,并畫成步驟圖,這里我們推薦 draw.io,一個(gè)非常不錯(cuò)的免費(fèi)畫圖工具。要假設(shè)每一個(gè)步驟都可能失敗,每一個(gè)步驟失敗后,都可能產(chǎn)生垃圾數(shù)據(jù),要提前設(shè)計(jì)好數(shù)據(jù)校驗(yàn)以及處理垃圾數(shù)據(jù)的方式。如果步驟之間有存在依賴關(guān)系,一定要在執(zhí)行下一步之前,調(diào)用 fsync(),以保證數(shù)據(jù)被持久化到磁盤中。
最后,設(shè)計(jì)和實(shí)現(xiàn)完成后,在單元測(cè)試和集成測(cè)試的過程中,也一定要增加故障測(cè)試。例如在單元測(cè)試中,通過 mock 的方式模擬 IO 故障,在集成測(cè)試中,可以加入隨機(jī) kill 進(jìn)程,隨機(jī)重啟服務(wù)器的測(cè)試用例,也可以通過 dm-delay,dm-flakey 等工具進(jìn)行磁盤故障模擬。
總結(jié)
看了這么多黑歷史,真的是三觀都?xì)У袅?。而事?shí)上,我們每天確實(shí)都生活在這些危機(jī)中。
這里要強(qiáng)調(diào)的是,我們并不是想詆毀 Linux 文件系統(tǒng),相反,我們非常感謝 Linux 內(nèi)核開發(fā)者在文件系統(tǒng)方面做出的貢獻(xiàn)。但同時(shí),由于系統(tǒng)的復(fù)雜度所帶來的嚴(yán)重問題也是無法回避的。在 Linux 文件系統(tǒng)的代碼中,必然還存在著很多未被發(fā)現(xiàn)的嚴(yán)重 Bug,開發(fā)者和研究人員也從來沒有停止過尋找 Bug 的努力。而隨著新功能不斷地加入,新的 Bug 也在不斷的產(chǎn)生。我們多一些這方面的思考和謹(jǐn)慎,并不是什么壞事。
感謝大家的觀看!
作者:張凱(Kyle Zhang) ,SmartX 聯(lián)合創(chuàng)始人 & CTO。SmartX 擁有國(guó)內(nèi)最頂尖的分布式存儲(chǔ)和超融合架構(gòu)研發(fā)團(tuán)隊(duì),是國(guó)內(nèi)超融合領(lǐng)域的技術(shù)領(lǐng)導(dǎo)者。
來源:SmartX知乎賬號(hào)
鏈接:https://zhuanlan.zhihu.com/p/28828826
————廣告時(shí)間————
《馬哥Linux云計(jì)算及架構(gòu)師》課程,由知名Linux布道師馬哥創(chuàng)立,經(jīng)歷了8年的發(fā)展,聯(lián)合阿里巴巴、唯品會(huì)、大眾點(diǎn)評(píng)、騰訊、陸金所等大型互聯(lián)網(wǎng)一線公司的馬哥課程團(tuán)隊(duì)的工程師進(jìn)行深度定制開發(fā),課程采用 Centos7.2系統(tǒng)教學(xué),加入了大量實(shí)戰(zhàn)案例,授課案例均來自于一線的技術(shù)案例。
開課時(shí)間:11月06號(hào)
課程咨詢請(qǐng)長(zhǎng)按即可咨詢

