我,研究雲系統中數據存儲的穩定性,決定測試自己,以確保我了解基本的東西。 我
在本文中,我探討了 Linux 文件 API 提供的持久性機制。 看來這裡一切都應該很簡單:程序調用命令 write()
, 該命令運行完成後,數據將安全地存儲在磁盤上。 但 write()
僅將應用程序數據複製到位於 RAM 中的內核緩存中。 為了強制系統將數據寫入磁盤,必須使用一些額外的機制。
總的來說,這份材料是一組筆記,與我在我感興趣的主題上學到的知識有關。 如果我們非常簡短地談論最重要的,事實證明,為了組織可持續的數據存儲,您需要使用命令 fdatasync()
或打開帶有標誌的文件 O_DSYNC
. 如果您有興趣了解更多關於數據在從代碼到磁盤的過程中發生的情況,請查看
使用 write() 函數的特點
系統調用 write()
標準中定義 write()
數據讀取操作必須準確返回先前寫入的字節,即使正在從其他進程或線程訪問數據(
這是否意味著該操作 write()
是原子的? 從技術角度來看,是的。 數據讀取操作必須返回全部或不返回任何寫入的內容 write()
. 但是手術 write()
,按照標準,不必結束,讓她寫下的都寫下了。 只允許寫入部分數據。 例如,我們可能有兩個流,每個流將 1024 字節附加到由相同文件描述符描述的文件。 從標準的角度來看,當每個寫操作只能向文件追加一個字節時,結果是可以接受的。 這些操作將保持原子性,但在它們完成後,它們寫入文件的數據將變得混亂。
fsync() 和 fdatasync() 函數
將數據刷新到磁盤的最簡單方法是調用函數 fdatasync()
。 在 fdatasync()
它說,在這個函數的運行過程中,如此大量的元數據被保存到磁盤,這是“正確執行以下數據讀取操作所必需的”。 而這正是大多數應用程序所關心的。
這裡可能出現的一個問題是這些機制不能保證在可能出現故障後可以找到該文件。 特別是,當創建一個新文件時,應該調用 fsync()
對於包含它的目錄。 否則,崩潰後,可能會發現該文件不存在。 之所以會這樣,是因為在UNIX下,由於使用了硬鏈接,一個文件可以存在於多個目錄中。 因此,調用時 fsync()
文件無法知道哪個目錄數據也應該刷新到磁盤(fsync()
到包含相應文件的目錄,但其他文件系統可能並非如此。
這種機制可以在不同的文件系統中以不同的方式實現。 我用了 fdatasync()
快一點 fsync()
. 公用事業 blktrace
表示 fdatasync()
通常將較少的數據寫入磁盤(在 ext4 中 fsync()
寫入 20 KiB,並且 fdatasync()
- 16 KiB)。 另外,我發現 XFS 比 ext4 稍快。 在這裡得到幫助 blktrace
能夠發現 fdatasync()
將較少的數據刷新到磁盤(XFS 中為 4 KiB)。
使用 fsync() 時的模棱兩可的情況
我可以想到三種模棱兩可的情況 fsync()
我在實踐中遇到過。
第一次此類事件發生在 2008 年。 當時,如果將大量文件寫入磁盤,Firefox 3 界面會“凍結”。 問題在於接口的實現使用 SQLite 數據庫來存儲有關其狀態的信息。 在界面中發生每次更改後,都會調用該函數 fsync()
,為穩定的數據存儲提供了良好的保障。 在當時使用的ext3文件系統中,函數 fsync()
將系統中的所有“臟”頁面刷新到磁盤,而不僅僅是那些與相應文件相關的頁面。 這意味著在 Firefox 中單擊一個按鈕可能會導致數兆字節的數據寫入磁盤,這可能需要很多秒。 據我了解,問題的解決方案
第二個問題發生在2009年。 然後,在系統崩潰後,新的 ext4 文件系統的用戶發現許多新創建的文件長度為零,但舊的 ext3 文件系統不會發生這種情況。 在上一段中,我談到了 ext3 如何將過多的數據轉儲到磁盤上,這大大降低了速度。 fsync()
. 為了改善這種情況,ext4 只刷新那些與特定文件相關的“臟”頁面。 與 ext3 相比,其他文件的數據在內存中保留的時間要長得多。 這樣做是為了提高性能(默認情況下,數據保持這種狀態 30 秒,您可以使用 fsync()
在需要提供穩定數據存儲並儘可能保護它們免受故障後果影響的應用程序中。 功能 fsync()
使用 ext4 比使用 ext3 更有效。 這種方法的缺點是,它的使用和以前一樣會減慢某些操作的速度,例如安裝程序。 查看詳情
第三個問題關於 fsync()
,起源於2018年。 然後,在PostgreSQL項目的框架內,發現如果函數 fsync()
遇到錯誤,它將“臟”頁面標記為“乾淨”。 結果,以下調用 fsync()
對此類頁面不執行任何操作。 因此,修改後的頁面存儲在內存中,永遠不會寫入磁盤。 這是一場真正的災難,因為應用程序會認為某些數據已寫入磁盤,但實際上並不會。 此類故障 fsync()
很少見,在這種情況下的應用程序幾乎無法解決問題。 如今,一旦發生這種情況,PostgreSQL 和其他應用程序就會崩潰。 O_SYNC
或帶有旗幟 O_DSYNC
. 使用這種方法,系統將報告執行特定數據寫入操作時可能發生的錯誤,但這種方法需要應用程序自己管理緩衝區。 閱讀更多相關信息
使用 O_SYNC 和 O_DSYNC 標誌打開文件
讓我們回到對提供持久數據存儲的 Linux 機制的討論。 即,我們正在談論使用標誌 O_SYNC
或標誌 O_DSYNC
使用系統調用打開文件時 write()
系統分別給出命令 fsync()
и fdatasync()
。 在 write()
и fdatasync()
). 這種方法的主要缺點是所有使用相應文件描述符的寫操作都將同步,這會限制構建應用程序代碼的能力。
使用帶 O_DIRECT 標誌的直接 I/O
系統調用 open()
支持國旗 O_DIRECT
,其設計目的是繞過操作系統緩存,執行 I/O 操作,直接與磁盤交互。 在許多情況下,這意味著程序發出的寫入命令將直接轉換為旨在使用磁盤的命令。 但是,總的來說,這種機制並不能替代功能 fsync()
或 fdatasync()
. 事實是磁盤本身可以 O_DIRECT
, O_DSYNC
,這意味著每個寫操作之後都會有一個調用 fdatasync()
.
事實證明,XFS 文件系統最近為 O_DIRECT|O_DSYNC
-數據記錄。 如果塊被覆蓋使用 O_DIRECT|O_DSYNC
,然後 XFS 將執行 FUA 寫入命令(如果設備支持),而不是刷新緩存。 我使用該實用程序驗證了這一點 blktrace
在 Linux 5.4/Ubuntu 20.04 系統上。 這種方法應該更有效,因為它將最少量的數據寫入磁盤並使用一次操作,而不是兩次(寫入和刷新緩存)。 我找到了一個鏈接
sync_file_range() 函數
Linux有一個系統調用 sync_file_range()
據說這個命令“非常危險”。 不建議使用它。 特點和危險 sync_file_range()
很好地描述了 fdatasync()
。 在 sync_file_range()
使用 ZFS 時不會將數據刷新到磁盤。 經驗告訴我,很少使用的代碼可能包含錯誤。 因此,除非絕對必要,否則我建議不要使用此系統調用。
有助於確保數據持久性的系統調用
我得出的結論是,可以使用三種方法來執行持久性 I/O 操作。 它們都需要一個函數調用 fsync()
對於創建文件的目錄。 這些是方法:
- 呼叫函數
fdatasync()
或fsync()
功能後write()
(更好地使用fdatasync()
). - 使用帶有標誌打開的文件描述符
O_DSYNC
或O_SYNC
(更好 - 有一面旗幟O_DSYNC
). - 命令用法
pwritev2()
有旗幟的RWF_DSYNC
或RWF_SYNC
(最好有旗幟RWF_DSYNC
).
性能說明
我沒有仔細衡量我調查的各種機制的性能。 我注意到他們工作速度的差異非常小。 這意味著我可能是錯的,在其他條件下,同樣的事情可能會顯示不同的結果。 首先,我將討論對性能影響較大的因素,然後再討論對性能影響較小的因素。
- 覆蓋文件數據比將數據附加到文件更快(性能提升可以達到 2-100%)。 將數據附加到文件需要對文件的元數據進行額外更改,即使在系統調用之後也是如此
fallocate()
, 但這種影響的大小可能會有所不同。 為了獲得最佳性能,我建議致電fallocate()
預先分配所需的空間。 那麼這個空間必須明確地用零填充並稱為fsync()
. 這將導致文件系統中相應的塊被標記為“已分配”而不是“未分配”。 這提供了一個小的(大約 2%)性能改進。 此外,某些磁盤的第一個塊訪問操作可能比其他磁盤慢。 這意味著用零填充空間可以顯著(大約 100%)性能提升。 特別是,這可能發生在磁盤上。亞馬遜電子服務系統 (這是非官方數據,我無法證實)。 存儲也是如此。GCP 永久性磁盤 (這已經是官方信息,已通過測試確認)。 其他專家也做了同樣的事情觀察 與不同的磁盤有關。 - 系統調用越少,性能越高(增益可以達到 5% 左右)。 好像是來電
open()
有旗幟的O_DSYNC
或致電pwritev2()
有旗幟的RWF_SYNC
更快的通話fdatasync()
. 我懷疑這裡的重點是,使用這種方法,解決相同任務所需執行的系統調用更少(一次調用而不是兩次)這一事實發揮了作用。 但性能差異非常小,因此您可以輕鬆忽略它並在應用程序中使用不會導致其邏輯複雜化的東西。
如果您對可持續數據存儲感興趣,這裡有一些有用的材料:
I/O 訪問方法 — 輸入/輸出機制的基礎概述。確保數據到達磁盤 - 一個關於數據在從應用程序到磁盤的過程中發生了什麼的故事。什麼時候應該 fsync 包含的目錄 - 何時申請的問題的答案fsync()
對於目錄。 簡而言之,事實證明您需要在創建新文件時執行此操作,此建議的原因是在 Linux 中可以有多個對同一文件的引用。Linux 上的 SQL Server:FUA 內部 - 這裡描述瞭如何在Linux平台上的SQL Server中實現持久化數據存儲。 這裡有一些 Windows 和 Linux 系統調用之間的有趣比較。 我幾乎可以肯定,正是由於這份資料,我才了解了 XFS 的 FUA 優化。
您曾經丟失過您認為安全地存儲在磁盤上的數據嗎?
來源: www.habr.com