Linux/UNIX編程如何保證文件落盤


本文轉載自Linux/UNIX編程如何保證文件落盤

導語

我們編寫程序write數據到文件中時,其實數據不會立馬寫入磁盤,而是會經過層層緩存。每層緩存都有自己的刷新時機,每層緩存都刷新后才會寫入磁盤。這些緩存的存在是為了加速讀寫操作,因為如果每次讀寫都對應真實磁盤操作,那么讀寫的效率會大大降低。帶來的壞處是如果期間發生掉電或者別的故障,還未寫入磁盤的數據就丟失了。對於數據安全敏感的應用,比如數據庫,比如交易程序,這是無法忍受的。所以操作系統提供了保證文件落盤的機制。我們來看下這些機制的原理和使用。

I/O緩沖區機制

img
圖片來自:https://lwn.net/Articles/457667/
上圖說明了操作系統到磁盤的數據流,以及經過的緩沖區。首先數據會先存在於應用的內存空間,如果調用庫函數寫入,庫函數可能還會把數據緩存在庫函數所維護的緩沖區空間中,比如C標准庫stdio提供的方法就會進行緩存,目的是為了減少系統調用的次數。這兩個緩存都是在用戶空間中的。庫函數緩存刷新時,會調用write系統調用寫入內核空間,內核同樣維護了一個頁緩存(page cache),操作系統會在合適的時間把臟頁的數據寫入磁盤。即使是寫入磁盤了,磁盤也可能維護了一個緩存,在這個時候掉電依然會丟失數據的,只有寫入了磁盤的持久存儲物理介質上,數據才是真正的落盤了,是安全的。我們接下來就是要研究如何做到這一步。

用戶空間緩沖區

用戶空間的緩存分為應用程序本身維護的緩沖區與庫維護的緩沖區。

應用本身維護的緩沖區需要開發者自己刷新,調用庫函數寫入到庫函數的緩沖區中。如果應用程序不依賴任何庫函數,而是直接使用系統調用,那么則是把數據寫入系統的緩沖區去。

庫函數一般都會維護緩沖區,目的是簡化應用程序的編寫,應用程序就不需要編寫維護緩沖區的代碼,同時性能也得到了提高,因為緩沖區大大減少了系統調用的次數,而系統調用是非常耗時的,系統調用涉及到用戶態到內核態的切換,這個切換需要很多的步驟與校驗,較為耗時。

比如C標准庫stdio就維護着一個緩沖區,對應這個緩沖區,C標准庫提供了fflush方法強制把緩沖區數據寫入操作系統。

Java的OutputStream接口提供了一個flush方法,具體的作用要看實現類的具體實現。BufferedOutputStream#flush就會把自己維護的緩沖區數據寫入下一層的OutputStream。如果是new BufferedOutputStream(new FileOutputStream("/"))這樣的模式,則調用BufferedOutputStream#flush會將數據寫入操作系統。

內核緩沖區

應用程序直接或者通過庫函數間接的使用系統調用write將數據寫入操作系統緩沖區。

UNIX系統在內核中設有高速緩存或頁面高速緩存。目的是為了減少磁盤讀寫次數。

用戶寫入系統的數據先寫入系統緩沖區,系統緩沖區寫滿后,將其排入輸出隊列,然后得到隊首時,才進行實際的IO操作。這種輸出方式被稱為延遲寫

UNIX系統提供了三個系統調用來執行刷新內核緩沖區:syncfsyncfdatasync

sync

void sync(void)

sync函數只是將所有修改過的塊緩沖區排入輸出隊列就返回,並不等待實際的寫磁盤操作返回。

操作系統的update系統守護進程會周期地調用sync函數,來保證系統中的數據能定期落盤。

根據sync(2) - Linux manual page的描述,Linux對sync的實現與POSIX規范不太一樣,POSIX規范中,sync可能在文件真正落盤前就返回,而Linux的實現則是文件真正落盤后才會返回。所以Linux中,syncfsync的效果是一樣的!但是1.3.20之前的Linux存在BUG,導致sync並不會在真正落盤后返回。

fsync

void fsync(int filedes)

fsync對指定的文件起作用,它傳輸內核緩沖區中這個文件的數據到存儲設備中,並阻塞直到存儲設備響應說數據已經保存好了。

fsync對文件數據與文件元數據都有效。文件的元數據可以理解為文件的屬性數據,比如文件的更新時間,訪問時間,長度等。

fdatasync

void fdatasync(int filedes)

fdatasyncfsync類似,兩者的區別是,fdatasync不一定需要刷新文件的元數據部分到存儲設備。

是否需要刷新文件的元數據,是要看元數據的變化部分是否對之后的讀取有影響,比如文件元數據的訪問時間st_atime和修改時間st_mtime變化了,fdatasync不會去刷新元數據數據到存儲設備,因為即使這個數據丟失了不一致了,也不影響故障恢復后的文件讀取。但是如果文件的長度st_size變化了,那么就需要刷新元數據數據到存儲設備。

所以如果你每次都更新文件長度,那么調用fsyncfdatasync的效果是一樣的。

但是如果更新能做到不修改文件長度,那么fdatasync能比fsync少了一次磁盤寫入,這個是非常大的速度提升。

O_SYNCO_DSYNC

除了上面三個系統調用,open系統調用在打開文件時,可以設置和同步相關的標志位:O_SYNCO_DSYNC

設置O_SYNC的效果相當於是每次write后自動調用fsync

設置O_DSYNC的效果相當於是每次write后自動調用fdatasync

關於新建文件

在一個文件上調用fsync/fdatasync只能保證文件本身的數據落盤,但是對於文件系統來說,目錄中也保存着文件信息,fsync/fdatasync的調用並不會保證這部分的數據落盤。如果此時發生掉電,這個文件就無法被找到了。

所以對於新建文件來說,還需要在父目錄上調用fsync

關於覆蓋現有文件

覆蓋現有文件時,如果發生掉電,新的數據是不會寫入成功,但是可能會污染現有的數據,導致現有數據丟失。

所以最佳實踐是新建一個臨時文件,寫入成功后,再替換原有文件。具體步驟:

  1. 新建一個臨時文件
  2. 向臨時文件寫入數據
  3. 對臨時文件調用fsync,保證數據落盤。期間發生掉電對現有文件無影響。
  4. 重命名臨時文件為目標文件名
  5. 對父目錄調用fsync

存儲設備緩沖區

存儲設備為了提高性能,也會加入緩存。高級的存儲設備能提供非易失性的緩存,比如有掉電保護的緩存。但是無法對所有設備做出這種保證,所以如果數據只是寫入了存儲設備的緩存的話,遇到掉電等故障,依然會導致數據丟失。

對於保證數據能保存到存儲設備的持久化存儲介質上,而不管設備本身是否有易失性緩存,操作系統提供了write barriers這個機制。

開啟了write barriers的文件系統,能保證調用fsync/fdatasync數據持久化保存,無論是否發生了掉電等其他故障,但是會導致性能下降。

許多文件系統提供了配置write barriers的功能。比如ext3, ext4, xfsbtrfsmount參數-o barrier表示開啟寫屏障,調用fsync/fdatasync能保證刷新存儲設備的緩存到持久化介質上。-o nobarrier則表示關閉寫屏障,調用fsync/fdatasync無法保證數據落盤。

Linux默認開啟寫屏障,所以默認情況下,我們調用fsync/fdatasync,就可以認為是文件真正的可靠落盤了。

對於這個層面的數據安全保證來說,應用程序是不需要去考慮的,因為如果這台機器的硬盤被掛載為沒有開啟寫屏障,那么可以認為這個管理員知道這個風險,他選擇了更高的性能,而不是更高的安全性。

總結

  • 文件數據從應用程序寫入磁盤,需要經過多個緩沖區:應用本身的緩沖區,庫的緩沖區,操作系統緩沖區,磁盤緩沖區
  • 如果文件數據只是寫入緩沖區,而還未寫入硬盤的持久化存儲設備上,那么斷電等故障會導致數據丟失
  • 庫層面刷新緩沖區:C標准庫的fflush,JDK的OutputStream#flush
  • 操作系統層面刷新緩沖區:
    • fsync可以刷新文件數據+元數據緩沖區
    • fdatasync可以刷新文件數據,在不影響讀取的情況下,可以不刷新文件元數據,性能更好一些
    • open系統調用的O_SYNC標志位可以在每次write后自動調用fsync
    • open系統調用的O_DSYNC標志位可以在每次write后自動調用fdatasync
  • 存儲設備層面刷新緩沖區:文件系統支持開啟/關閉寫屏障write barriers,如果開啟寫屏障,則fsync/fdatasync可以保證文件寫入磁盤的持久化設備中,如果關閉寫屏障,則fsync/fdatasync只能保證文件寫入磁盤,此時文件可能存在於磁盤的緩存中

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM