本文轉載自Linux/UNIX編程如何保證文件落盤
導語
我們編寫程序write數據到文件中時,其實數據不會立馬寫入磁盤,而是會經過層層緩存。每層緩存都有自己的刷新時機,每層緩存都刷新后才會寫入磁盤。這些緩存的存在是為了加速讀寫操作,因為如果每次讀寫都對應真實磁盤操作,那么讀寫的效率會大大降低。帶來的壞處是如果期間發生掉電或者別的故障,還未寫入磁盤的數據就丟失了。對於數據安全敏感的應用,比如數據庫,比如交易程序,這是無法忍受的。所以操作系統提供了保證文件落盤的機制。我們來看下這些機制的原理和使用。
I/O緩沖區機制
圖片來自: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系統提供了三個系統調用來執行刷新內核緩沖區:sync
,fsync
,fdatasync
。
sync
void sync(void)
sync
函數只是將所有修改過的塊緩沖區排入輸出隊列
就返回,並不等待實際的寫磁盤操作返回。
操作系統的update
系統守護進程會周期地調用sync
函數,來保證系統中的數據能定期落盤。
根據sync(2) - Linux manual page的描述,Linux對sync
的實現與POSIX規范不太一樣,POSIX規范中,sync
可能在文件真正落盤前就返回,而Linux的實現則是文件真正落盤后才會返回。所以Linux中,sync
與fsync
的效果是一樣的!但是1.3.20之前的Linux存在BUG,導致sync並不會在真正落盤后返回。
fsync
void fsync(int filedes)
fsync
對指定的文件起作用,它傳輸內核緩沖區中這個文件的數據到存儲設備中,並阻塞直到存儲設備響應說數據已經保存好了。
fsync
對文件數據與文件元數據都有效。文件的元數據可以理解為文件的屬性數據,比如文件的更新時間,訪問時間,長度等。
fdatasync
void fdatasync(int filedes)
fdatasync
和fsync
類似,兩者的區別是,fdatasync
不一定需要刷新文件的元數據部分到存儲設備。
是否需要刷新文件的元數據,是要看元數據的變化部分是否對之后的讀取有影響,比如文件元數據的訪問時間st_atime
和修改時間st_mtime
變化了,fdatasync
不會去刷新元數據數據到存儲設備,因為即使這個數據丟失了不一致了,也不影響故障恢復后的文件讀取。但是如果文件的長度st_size
變化了,那么就需要刷新元數據數據到存儲設備。
所以如果你每次都更新文件長度,那么調用fsync
和fdatasync
的效果是一樣的。
但是如果更新能做到不修改文件長度,那么fdatasync
能比fsync
少了一次磁盤寫入,這個是非常大的速度提升。
O_SYNC
和O_DSYNC
除了上面三個系統調用,open
系統調用在打開文件時,可以設置和同步相關的標志位:O_SYNC
和O_DSYNC
。
設置O_SYNC
的效果相當於是每次write
后自動調用fsync
。
設置O_DSYNC
的效果相當於是每次write
后自動調用fdatasync
。
關於新建文件
在一個文件上調用fsync
/fdatasync
只能保證文件本身的數據落盤,但是對於文件系統來說,目錄中也保存着文件信息,fsync
/fdatasync
的調用並不會保證這部分的數據落盤。如果此時發生掉電,這個文件就無法被找到了。
所以對於新建文件來說,還需要在父目錄上調用fsync
。
關於覆蓋現有文件
覆蓋現有文件時,如果發生掉電,新的數據是不會寫入成功,但是可能會污染現有的數據,導致現有數據丟失。
所以最佳實踐是新建一個臨時文件,寫入成功后,再替換原有文件。具體步驟:
- 新建一個臨時文件
- 向臨時文件寫入數據
- 對臨時文件調用
fsync
,保證數據落盤。期間發生掉電對現有文件無影響。 - 重命名臨時文件為目標文件名
- 對父目錄調用
fsync
存儲設備緩沖區
存儲設備為了提高性能,也會加入緩存。高級的存儲設備能提供非易失性的緩存,比如有掉電保護的緩存。但是無法對所有設備做出這種保證,所以如果數據只是寫入了存儲設備的緩存的話,遇到掉電等故障,依然會導致數據丟失。
對於保證數據能保存到存儲設備的持久化存儲介質上,而不管設備本身是否有易失性緩存,操作系統提供了write barriers
這個機制。
開啟了write barriers
的文件系統,能保證調用fsync
/fdatasync
數據持久化保存,無論是否發生了掉電等其他故障,但是會導致性能下降。
許多文件系統提供了配置write barriers
的功能。比如ext3
, ext4
, xfs
和 btrfs
。mount
參數-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
只能保證文件寫入磁盤,此時文件可能存在於磁盤的緩存中
參考資料
- 《UNIX環境高級編程》
- Ensuring data reaches disk
- linux 同步IO: sync、fsync與fdatasync - CSDN博客
- sync(2) - Linux manual page
- fsync(2) - Linux manual page
- sync/fsync/fdatasync的簡單比較 - CSDN博客
- Everything You Always Wanted To Know About fsync() - xavier roche’s homework
- Linux OS: Write Barriers - 德哥@Digoal的日志 - 網易博客
- Linux Barrier I/O 實現分析與barrier內存屏蔽分析總結 - 綜合編程類其他綜合 - 紅黑聯盟
- Chapter 16. Write Barriers
- [Barriers and journaling filesystems LWN.net]