三、崩潰一致性問題(The Crash Consistency Problem)
從這些崩潰場景中,希望您可以看到我們的磁盤文件系統image因崩潰而可能發生的許多問題:我們可能在文件系統資料結構中存在不一致;我們可以有空間洩漏;我們可以將垃圾資料返回給使用者;等等。我們希望理想的做法是將文件系統從一個一致的狀態(例如,在文件附加到另一個狀態之前)移動到另一個狀態(例如,inode,bitmap和新的Data Blocks已經寫入磁盤之後)。不幸的是,我們不能輕易做到這一點,因為磁盤一次只提交一次寫入,並且在這些更新之間可能會發生崩潰或斷電。我們稱這個一般問題為崩潰一致性問題(我們也稱之為一致性更新問題)。
解決方案#1:文件系統檢查工具(The File System Checker)
早期的文件系統採用了一種簡單的方法來確保崩潰一致基本上,他們決定讓不一致發生,然後修復(重新啟動時)。這種懶惰方法的典型例子是在一個工具中找到的:fsck2。fsck是用於查找這些不一致並修復它們的UNIX工具[M86];用於檢查和修復磁盤分區的類似工具存在於不同的系統上。請注意,這種方法無法解決所有問題;例如,考慮以上情況,文件系統看起來一致但inode指向垃圾資料。唯一真正的目標是確保文件系統Metadata內部一致。
正如McKusick和Kowalski的論文[MK96]所總結的那樣,fsck工具分幾個階段運行。它在文件系統安裝並可用之前運行(fsck假定運行時沒有其他文件系統活動正在進行);一旦完成,磁盤上的文件系統應該是一致的,因此可以被使用者訪問。
以下是fsck所做的一個基本總結:
- Superblock:
fsck首先檢查超級塊是否看起來合理,主要是進行完整性檢查,例如確保文件系統大小大於已分配的塊數。通常這些健康檢查的目標是找到一個可疑(崩貴的)超級塊;在這種情況下,系統(或管理員)可能會決定使用超級塊的備用副本。 -
Freeblocks:
接下來,fsck掃描inode,indirect blocks,double indirect blocks等,以了解當前在文件系統中分配了哪些塊。它使用這些知識來生成正確版本的分配bitmap;因此,如果bitmap和inode之間存在任何不一致,則透過信任inode內的訊息來解決它。對所有inode執行相同類型的檢查,確保inode,bitmap中標記了所有類似inode的inode。 -
Inodestate:
檢查每個inode是否存在損壞或其他問題。例如,fsck使每個分配的inode具有有效的類型字段(例如,常規文件,目錄,符號鏈接等)。如果inode字段出現問題並不容易修復,則inode將被視為可疑並由fsck清除;inode bitmap會相應更新。 - Inodelinks:
fsck還會驗證每個分配的鏈接數。您可能還記得,鏈接數指示包含對此特定文件的引用(即鏈接)的不同目錄的數量。要驗證鏈接計數,fsck會掃描整個目錄樹,從根目錄開始,然後為文件系統中的每個文件和目錄構建自己的鏈接計數。如果新計算的計數與inode內發現的計數不匹配,則必須採取糾正措施,通常透過將計數固定在inode中。如果發現分配的inode但沒有目錄引用它,它將移動到lost+found目錄。 - Duplicates:
fsck還檢查重複的指針,即兩個不同inode引用同一個塊的情況。如果一個inode非常糟糕,它可能會被清除。或者,可以復制指向的塊,從而根據需要為每個inode提供自己的副本。 - Badblocks:
在掃描所有指針列表時,還會執行壞塊指針檢查。如果指針指向超出其有效範圍的某個指針,則該指針被認為是“不好的”,例如,它的地址指的是大於分區大小的塊。在這種情況下,fsck不能做任何太聰明的事情;它只是從inode或間接塊中刪除(清除)指針。 - Directorychecks:
fsck不理解使用者文件的內容;但是,目錄保存由文件系統本身創建的專門格式化的訊息。因此,fsck會對每個目錄的內容執行額外的完整性檢查,確保“。”和“..”是第一個條目,分配目錄條目中引用的每個inode的inode,並確保沒有目錄鏈接到在整個等級中不止一次。
正如你所看到的,構建一個工作fsck需要復雜的文件系統知識;確保這樣一段代碼在所有情況下都能正常工作,這可能是具有挑戰性的[G+08]。然而,fsck(以及類似的方法)有一個更大或許更根本的問題:它們太慢了。使用非常大的磁盤捲時,掃描整個磁盤以查找所有分配的塊並讀取整個目錄樹可能需要幾分鐘或幾小時。隨著磁盤容量的增長以及RAID越來越流行,fsck的性能變得越來越低,儘管最近取得了進展[M+13])。
在更高層次上,fsck的基本前提似乎只是一種非理性。考慮我們上面的例子,其中只有三個塊被寫入磁盤;掃描整個磁盤以修復僅更新三個塊時出現的問題的成本非常高昂。這種情況類似於將你的鑰匙放在臥室的地板上,然後開始搜索整個房子的鑰匙恢復算法,從地下室開始,在每個房間里工作。它的工作原理很浪費。因此,隨著磁盤(和RAID)的增長,研究人員和從業者開始尋找其他解決方案。
解決方案2:日誌文件系統Journaling
(或預寫日誌文件系統or Write-Ahead Logging)
可能是一致性更新問題最流行的解決方案是從資料庫管理系統的世界竊取一個想法。這個想法被稱為預寫式日誌記錄,是為了解決這類問題而發明的。在文件系統中,出於歷史原因我們通常稱之為提前寫入日誌記錄。第一個文件系統是Cedar[H87],儘管許多現代文件系統都使用這個想法,包括Linuxext3和ext4,reiserfs,IBM的JFS,SGI的XFS和WindowsNTFS。
基本思路如下。在更新磁盤時,在寫入結構之前,首先寫下一個小記錄(磁盤上的其他位置,位於眾所周知的位置),描述您將要執行的操作。編寫本說明是“提前寫入”部分,並將其寫入我們組織為“日誌”的結構;因此,預寫日誌。
透過將註釋寫入磁盤,您可以保證,如果在更新(覆蓋)更新結構期間發生崩潰,則可以返回並查看所做的註釋並重試;因此,在崩潰之後,您將確切地知道要修復哪些內容(以及如何修復它),而不必掃描整個磁盤。透過設計,日記可以在更新期間添加一些工作,以大大減少恢復過程中所需的工作量。
現在我們將描述一個流行的日誌文件系統Linuxext3如何將日誌整合到文件系統中。大多數磁盤上的結構與Linuxext2相同,例如,磁盤分為block groups,每個block groups都有inode和資料bitmap以及inode和Data Blocks。新的關鍵結構是日誌本身,它佔用了分區內或其他設備上的少量空間。因此,一個ext2文件系統(沒有日誌)看起來像這樣:
假設日誌放置在同一個文件系統image中(儘管有時它放置在單獨的設備上,或作為文件系統中的文件),帶日誌的ext3文件系統如下所示:
真正的區別就在於Journal的存在,當然還有它的使用方式。
數據日誌(Data Journaling)
讓我們看一個簡單的例子來了解資料日記如何工作。資料日記可作為Linuxext3文件系統的一種模式提供,大部分討論都基於這種模式。
假設我們再次有我們的規範更新,我們希望將inode(I[v2]),bitmap(B[v2])和Data Blocks(Db)再次寫入磁盤。在將它們寫入其最終磁盤位置之前,我們現在首先將它們寫入日誌(a.k.a.日誌)。這就是日誌中的樣子:
你可以看到我們在這裡寫了五個blocks。Transaction開始(TxB)告訴我們關於此更新的訊息,包括關於文件系統的等待更新的訊息(例如,塊I[v2],B[v2]和Db的最終地址)以及某種的Transaction identifier (TID)。中間的三個blocks只包含他們自己blocks的確切內容;這就是所謂的physical logging,因為我們在日誌中放置了更新的確切physical contents(另一種想法,邏輯日誌記錄,在日誌中放置了一個更緊湊的更新邏輯表示,例如,“此更新希望追加資料阻止Db到文件X“,這稍微複雜一些,但可以節省日誌空間並提高性能)。最後的塊(TxE)是該transaction結束的標記,並且還包含TID。
一旦這個transaction安全地在磁盤上,我們就可以覆蓋文件系統中的舊結構;這個過程被稱為檢查點。因此,為了檢查文件系統(即,使其與日誌中的等待更新保持同步),我們向其磁盤位置寫入I[v2],B[v2]和Db,如上所見;如果這些寫入成功完成,我們已成功檢查了文件系統並基本完成。因此,我們最初的操作順序是:
- Journalwrite:
將包括transaction開始塊,所有待處理資料和Metadata更新以及transactionend的transaction寫入日誌;等待這些寫入完成。 - Checkpoint:
將未完成的Metadata和資料更新寫入文件系統中的最終位置。
在我們的例子中,我們首先將TxB,I[v2],B[v2],Db和TxE寫入日誌。當這些寫入完成時,我們將透過將I[v2],B[v2]和Db檢查點到磁盤上的最終位置來完成更新。
ASIDE: FORCING WRITES TO DISK’
為了強制在兩個磁盤寫入之間進行排序,現代文件系統必須採取一些額外的預防措施。在過去,強制在兩次寫入之間進行排序,A和B很容易:只需向磁盤寫入A,等待寫入完成後磁盤中斷OS,然後再寫入B.
由於磁盤內寫入緩存的使用增加,事情變得稍微複雜一些。啟用寫入緩衝(有時稱為即時報告)時,當磁盤放置在磁盤的memory高速緩存中且尚未到達磁盤時,磁盤將通知操作系統寫入已完成。如果操作系統隨後發出後續寫入,則不能保證在先前的寫入之後到達磁盤;因此寫入之間的順序不會被保留。一種解決方法是禁用寫入緩衝。但是,更現代化的系統需要額外的預防措施並提出明確的寫入障礙;這種障礙在完成時會保證在屏障之前發出的所有寫入將在屏障之後發出的任何寫入之前到達磁盤。
所有這些機器都需要對磁盤的正確操作有很大的信任。不幸的是,最近的研究表明,一些磁盤製造商努力提供“更高性能”的磁盤,顯然忽略了寫入障礙請求,從而使磁盤看起來運行得更快,但存在操作不正確的風險[C+13,R+11]。
As Kahan said,the fast almost always beats out the slow, even if the fast is wrong.
在日誌寫入期間發生崩潰時,事情會變得更加棘手。這裡,我們試圖將transaction中的set of blocks(例如,TxB,I[v2],B[v2],Db,TxE)寫入磁盤。一個簡單的方法是每次發行一個,等待每個完成,然後發布下一個。但是,這很慢。
理想情況下,我們希望一次發出所有5個塊寫入,因為這會將5次寫入轉換為單次順序寫入,因此速度更快。但是,這是不安全的,原因如下:給予這麼大的寫入,磁盤內部可以執行調度並以任何順序完成大寫的小部分。因此,磁盤內部可能(1)寫入TxB,I[v2],B[v2]和TxE,並且稍後(2)寫入Db。不幸的是,如果磁盤在(1)和(2)之間斷電,這就是磁盤上的結果:
為什麼這是個問題?那麼,這個transaction就像一個有效的transaction(它有一個開始和結束的序列號)。此外,文件系統無法查看第四個塊並知道它是錯誤的;畢竟,這是任何的使用者資料。因此,如果系統現在重新啟動並運行恢復,它將replay此transaction,並無意中將垃圾塊’??’的內容複製到Db應該存在的位置。這對於文件中的任何使用者資料是不利的;如果它發生在一個重要的文件系統中,比如超級塊,它可能會導致文件系統無法掛載,那就更糟了。
ASIDE:OPTIMIZING LOG WRITES
您可能已經注意到寫入日誌的特別低效率。即,文件系統首先必須寫出交易開始塊和交易的內容;只有在這些寫入完成後,文件系統才能將transaction結束塊發送到磁盤。如果考慮磁盤的工作方式,很明顯會影響性能:通常會產生額外的旋轉(想一想為什麼)。
我們以前的研究生之一VijayanPrabhakaran有一個簡單的想法來解決這個問題[P+05]。在向日記寫入交易時,將日記內容的校驗和包括在開始和結束欄中。這樣做可以使文件系統立即寫入整個transaction,而不會發生等待;如果在恢復期間,文件系統在計算的校驗和與transaction中儲存的校驗和之間看到不匹配,則可以斷定在寫入transaction期間發生崩潰並因此丟棄文件系統更新。因此,透過在寫入協議和恢復系統中進行小小的調整,文件系統可以實現更快的常見性能;最重要的是,系統稍微更可靠,因為從日誌中讀取的任何資料現在都受到校驗和的保護。
這個簡單的修復很有吸引力,可以獲得Linux文件系統開發人員的注意,後者然後將其整合到下一代Linux文件系統中,稱為(你猜對了!)Linux ext4。它現在在全球數百萬台機器上發貨,包括Android手持平台。因此,每當您在許多基於Linux的系統上寫入磁盤時,Wisconsin開發的一小段代碼都會使您的系統更快更可靠。
為避免此問題,文件系統分兩步發出transaction性寫入。首先,它將除TxE塊之外的所有塊寫入日誌,並一次發出這些寫入。當這些寫入完成時,日誌將看起來像這樣(假設我們再次追加workload):
當這些寫入完成時,文件系統發出寫入TxE塊,從而使日誌處於最終的安全狀態:
這個過程的一個重要方面是磁盤提供的原子性保證。事實證明,磁盤保證任何512byte的,不論寫入發生或未發生(並且永遠不會被寫入一半);因此,為了確保TxE的寫入是原子的,應該使其成為單個512byte的塊。因此,我們目前的協議來更新文件系統,其三個階段分別標記為:
1.Journalwrite::
將transaction的內容(包括TxB,Metadata和資料)寫入日誌;等待這些寫入完成。
2.Journalcommit::
將transaction提交塊(包含TxE)寫入日誌;等待寫入完成;交易據說是承諾的。
3.Checkpoint:
將更新內容(Metadata和資料)寫入其最終的磁盤位置。
恢復(Recovery)
現在我們來了解文件系統如何使用日誌的內容從崩潰中恢復。在這一系列更新過程中,任何時候都可能發生崩潰。如果在將transaction安全地寫入日誌之前發生崩潰(即,在上面的步驟2完成之前),那麼我們的工作很簡單:掛起的更新將被簡單地跳過。如果崩潰發生在transaction已經提交到日誌之後,但在檢查點完成之前,則文件系統可以如下方式恢復更新。系統引導時,文件系統恢復進程將掃描日誌並查找已提交給磁盤的transaction;這些transaction因此被replay(按順序),文件系統再次嘗試將transaction中的塊寫出到其最終的磁盤位置。這種記錄形式是最簡單的形式之一,被稱為重做日誌記錄。透過恢復日誌中已提交的transaction,文件系統確保磁盤上的結構是一致的,因此可以透過掛載文件系統並為新請求自行準備進行。
請注意,即使在blocks的最終位置的某些更新完成後,在檢查指向期間的任何時間發生崩潰也是可以被接受的。在最糟糕的情況下,這些更新中的一部分在恢復過程中會再次執行。因為恢復是一種罕見的操作(只發生在意外的系統崩潰之後),所以一些冗餘寫入是無需擔心的。
批處理日誌更新(Batching Log Updates)
您可能已經注意到基本協議可能會增加大量額外的磁盤流量。例如,假設我們在同一個目錄中創建了兩行文件,名為file1和file2。要創建一個文件,必須更新多個磁盤結構,最低限度包括:inode bitmap(分配新的inode),新創建的文件inode,包含新文件的父目錄的Data Blocks目錄項,以及父目錄inode(現在有一個新的修改時間)。使用日誌功能,我們在邏輯上將所有這些訊息提交給我們的兩個文件創作中的每一個;因為這些文件位於同一個目錄中,並且假設他們甚至在同一個inode塊中有inode,這意味著如果我們不小心,我們最終會反複寫入這些相同的塊。
為了解決這個問題,一些文件系統不會每次更新一個磁盤(例如Linux ext3)。相反,可以將所有更新緩衝到global transaction中。在我們上面的例子中,當創建兩個文件時,文件系統將memoryinode bitmap,文件的inode,目錄資料和目錄inode標記為臟,並將它們添加到組成當前塊的列表交易。當最終時間將這些blocks寫入磁盤時(例如,在5秒超時後),這個單個global transaction將被提交,其中包含上述所有更新。因此,透過緩衝更新,文件系統在很多情況下可以避免過多的磁盤寫入流量。
使日誌有限(Making The Log Finite)
因此,我們已經達成了更新文件系統磁盤結構的基本協議。文件系統在memory中緩存更新一段時間;當最後一次寫入磁盤時,文件系統首先仔細地將交易的細節寫到日誌(a.k.a.預先寫好的日誌)中;transaction完成後,文件系統會將這些blocks檢查到磁盤上的最終位置。
但是,日誌的大小是有限的。如果我們不斷向它添加交易(如本圖所示),它將很快填滿。那麼你認為會發生什麼?
日誌變滿時出現兩個問題。第一種方法比較簡單,但不太重要:日誌越大,恢復時間就越長,因為恢復過程必須replay日誌中的所有transaction(按順序)才能恢復。第二個問題更為嚴重:當日誌滿(或接近滿)時,不能再向磁盤提交更多transaction,從而使文件系統“不夠用”(即無用)。
為了解決這些問題,日誌文件系統將日誌視為循環資料結構,反復重複使用日誌;這就是為什麼Journal有時被稱為循環日誌。為此,文件系統必須在檢查點後一段時間採取行動。具體來說,一旦transaction已經檢查點,文件系統應該釋放它在日誌中佔用的空間,從而允許重用日誌空間。有很多方法可以達到這個目的;例如,您可以簡單地在日誌超級塊中的日誌中標記最早和最新的非檢查點transaction;所有其他空間都是free的。以下是一個圖形描述:
在日誌超級塊中(不要與主文件系統超級塊混淆),日誌記錄系統會記錄足夠的訊息以知道哪些transaction尚未檢查點,從而減少恢復時間,並且可以再重新使用日誌circular fashion。因此我們在基本協議中增加了另一個步驟:
1.Journalwrite:
將transaction的內容(包含TxB和更新的內容)寫入日誌;等待這些寫入完成。
2.Journalcommit:
將transaction提交塊(包含TxE)寫入日誌;等待寫入完成;交易現在承諾。
3.Checkpoint:
將更新的內容寫入文件系統中的最終位置。
4.Free:
一段時間後,透過更新日記帳超級塊來在日記中標記交易。
因此,我們有我們的最終資料日記協議。但是仍然存在一個問題:我們將每個Data Blocks寫入磁盤兩次,這是一項沉重的代價,特別是對於像系統崩潰那樣罕見的事情。你能想出一種保持一致性的方法,而不用兩次寫入資料嗎?
Metadata 日記(Metadata Journaling)
雖然現在恢復速度很快(掃描日誌並replay一些transaction而不是掃描整個磁盤),但是文件系統的正常運行速度比我們想要的要慢。特別是,對於每次寫入磁盤,我們現在也首先寫入日誌,從而使寫入流量翻倍;在連續寫入workload期間,這種加倍是特別痛苦的,現在這種情況將會以驅動器峰值寫入帶寬的一半進行。此外,在寫入日誌和寫入主文件系統之間,存在代價高昂的seek,這為一些workload增加了明顯的overhead。
由於將每個Data Blocks寫入磁盤兩次的成本很高,所以人們嘗試了一些不同的方法來提高性能。例如,我們上面描述的日誌模式通常被稱為資料日誌(如在Linux ext3中),因為它記錄了所有使用者資料(除了文件系統的Metadata之外)。一種更簡單(也是更常見)的日誌形式有時被稱為有序日誌(或者僅僅是Metadata日誌),除了使用者資料沒有寫入日誌之外,它幾乎是相同的。因此,當執行與上述相同的更新時,以下訊息將被寫入日誌中:
先前寫入日誌的Data Blocks Db將改為寫入文件系統,避免額外寫入;考慮到大多數磁盤的I/O流量都是資料,而不是兩次寫入資料會大大減少日誌的I/O load。不過,修改的確引發了一個有趣的問題:我們應該在什麼時候將Data Blocks寫入磁盤?
讓我們再次考慮一個文件的示例附件,以更好地理解問題。更新由三個block groups成:I[v2],B[v2]和Db。前兩個都是Metadata,將被記錄,然後檢查指出;後者只會被寫入一次文件系統。我們應該什麼時候將Db寫入磁盤?有關係嗎?
事實證明,資料寫入的順序對於僅記錄Metadata而言是重要的。例如,如果我們在transaction(包含I[v2]和B[v2])完成後將Db寫入磁盤會怎樣?不幸的是,這種方法存在一個問題:文件系統是一致的,但是我[v2]最終可能指向垃圾資料。具體來說,考慮寫入I[v2]和B[v2]但Db沒有將其寫入磁盤的情況。文件系統然後會嘗試恢復。由於Db不在日誌中,因此文件系統將replay寫入I[v2]和B[v2],並生成一致的文件系統(從文件系統Metadata的角度來看)。然而,I[v2]將指向垃圾資料,即在Db所在的插槽中的任何位置。
為確保不會出現這種情況,有些文件系統(例如Linux ext3)會在將相關Metadata寫入磁盤之前,先將(常規文件的)Data Blocks寫入磁盤。具體來說,協議如下:
- Data write:
將資料寫入最終位置;等待完成(等待是可選的;詳情見下文)。 - Journal metadata write:
將開始塊和Metadata寫入登錄;等待寫入完成。 - Journal commit:
編寫transaction提交塊(包含TxE)到日誌;等待寫入完成;交易(in包括資料)現在已經提交。 - Check point metadata:
寫入Metadata更新的內容到文件系統中的最終位置。 - Free:
稍後,在日記超級分塊中將交易標記為free。
透過搶先強制地將資料寫入,文件系統可以保證指針永遠不會指向垃圾。事實上,“在指向它的對象之前寫入指向的對象”這一規則是崩潰一致性的核心,甚至可以被其他崩潰一致性方案[GP94](詳見下文)進一步利用。
在大多數係統中,Metadata日記(類似於ext3的有序日記)比完整資料日記更受歡迎。例如,Windows NTFS和SGI的XFS都使用某種形式的Metadata日記。Linuxe xt3提供了資料不同的選項,ordered或unordered模式的選項(unordered模式下,資料可以隨時寫入)。所有這些模式保持Metadata一致;它們在資料的semantics上有所不同。
最後,請注意,正如上面的協議所述,強制資料寫入完成(步驟1)在寫入日誌(步驟2)之前不需要正確性。具體來說,將資料寫入以及transaction-begin block和Metadata發布給日誌將是很好的方式;唯一要求是在發布日誌提交塊之前完成步驟1和步驟2(步驟3)。
TrickyCase:BlockReuse(整蠱案例:阻止重用)
有一些有趣的corner cases使得日記更加棘手,因此值得討論。其中有許多是圍繞著塊重用;正如StephenTweedie(ext3背後的主要力量之一)所說:
“整個系統的可怕部分是什麼?…它正在刪除文件。所有與刪除有關的東西都很驚悚。所有與刪除有關的事情……如果塊被刪除然後重新分配,會發生什麼情況。“[T00]
Tweedie給出的具體例子如下。假設您正在使用某種形式的Metadata日記(因此文件的Data Blocks未被記錄)。假設您有一個名為foo的目錄。使用者向foo添加一個條目(比如透過創建一個文件),因此foo的內容(因為目錄被認為是Metadata)被寫入日誌;假設foo目錄資料的位置是塊1000.日誌因此包含如下所示的內容:
此時,使用者刪除目錄中的所有內容以及目錄本身,釋放塊1000以供重用。最後,使用者創建一個新文件(比如foobar),這個文件最終重用了曾經屬於foo的同一塊(1000)。foobar的inode和它的資料一樣,都是致力於磁盤的;但是,請注意,因為Metadata日記正在使用中,所以只有foobar的inode被委託給Journal;而 block 1000新寫入的foobar file資料不被記錄。
現在假設發生崩潰並且所有這些訊息仍在日誌中。在replay期間,恢復過程只是replay日誌中的所有內容,包括在block 1000中寫入目錄資料;replay因此用舊目錄內容覆蓋當前文件foobar的使用者資料!顯然,這不是一個正確的recovery action,當使用者讀取foobar文件時會感到驚訝。
這個問題有很多解決方案。例如,可以不重複塊,直到將所述塊的刪除檢查點排除在日誌外。Linux ext3所做的是向日誌中添加一種新的記錄類型,稱為撤銷記錄。在上面的情況下,刪除目錄會導致將撤銷記錄寫入日誌。replay Journal時,系統首先掃描這些撤銷記錄;任何這樣的撤銷資料都不會replay,從而避免了上述問題。
Wrapping Up Journaling : A Timeline(總結日記:時間軸)
在結束我們對日誌的討論之前,我們總結了我們討論過的協議,並用時間線描述了它們中的每一個。圖42.1顯示了記錄資料和Metadata時的協議,而圖42.2顯示了僅記錄Metadata時的協議。
在每個圖中,時間向下增加,圖中的每一行顯示寫入可以發出或可能完成的邏輯時間。例如,在資料日誌記錄協議(圖42.1)中,transaction begin block(TxB)的寫入和transaction的內容可以在邏輯上同時發布,因此可以按任何順序完成;然而,寫入transaction end block(TxE)必須等到先前的寫入完成後再發出。同樣,檢查點寫入資料和Metadata塊不能開始,直到交易結束塊已提交。水平虛線表示必須遵守寫入順序的要求。
針對Metadata日記協議顯示類似的時間線。請注意,資料寫入可以在寫入transaction開始和日誌內容的同時在邏輯上發出;但是,它必須在交易結束前發布並完成。
最後,請注意在時間軸中為每個寫入標記的完成時間是任何的。在真實係統中,完成時間由I/O子系統決定,它可能會重新排序寫入以提高性能。關於命令我們有的唯一保證是必須為協議正確執行的那些(並且透過圖中的水平虛線顯示)。
解決方案3:其他方法(Other Approaches)
到目前為止,我們已經描述了兩種保持文件系統Metadata一致的選擇:基於fsck的懶惰方法和稱為日誌的更為活躍的方法。但是,這不是唯一的兩種方法。Ganger和Patt引入了一種稱為SoftUpdates[GP94]的方法。此方法仔細地命令對文件系統的所有寫入操作,以確保磁盤上的結構永遠不會處於不一致的狀態。例如,透過在指向它的inode之前寫入指向Data Blocks的磁盤,我們可以確保inode從不指向垃圾;可以為文件系統的所有結構導出類似的規則。然而,實施軟件更新可能是一個挑戰。儘管上述日誌層可以在相對較少的文件系統結構知識的情況下實現,但軟件更新需要每個文件系統資料結構的複雜知識,並因此增加了系統的相當複雜度。
另一種方法稱為copy-on-write(yes,COW),並在許多流行的文件系統中使用,包括Sun的ZFS[B07]。這種技術不會覆蓋文件或目錄;相反,它將新的更新置於磁盤上以前未使用的位置。完成多次更新後,COW文件系統會翻轉文件系統的根結構,以包含指向新更新結構的指針。這樣做可以使文件系統保持一致。當我們在未來的章節中討論日誌結構文件系統(LFS)時,我們將更多地了解這種技術;LFS是COW的早期例子。
另一種方法是我們剛剛在威斯康辛州開發的方法。在這種名為基於反向指示器的一致性(或BBC)的技術中,不會在寫入之間執行排序。為了實現一致性,系統中的每個block都會添加一個額外的返回指針;例如,每個Data Blocks都具有對其所屬的inode的引用。當訪問文件時,文件系統可以透過檢查前向指針(例如inode或直接塊中的地址)是否指向返回給它的塊來確定文件是否一致。如果是這樣,一切都必須安全地到達磁盤,因此文件是一致的;如果不是,則文件不一致,並返回錯誤。透過向文件系統添加回指針,可以獲得一種新的惰性崩潰一致性[C+12]。
最後,我們還探討了減少日誌協議等待磁盤寫入完成的次數的技術。題為樂觀的崩潰一致性[C+13],這種新方法盡可能多地向磁盤寫入資料,並使用transaction校驗和[P+05]的一般形式以及其他一些技術來檢測它們出現時的不一致性。對於一些workload,這些樂觀的技術可以將性能提高一個數量級。但是,為了真正正常工作,需要稍微不同的磁盤接口[C+13]。