寫檔案後呼叫 filestream.close; filestream.flush; 或者 using (filestream fs = new filestream(…)) {} ,檔案是否被實際寫入了磁碟?可能大多數人都會說肯定會寫入磁碟,但我要告訴你,不一定!
我所在的公司有上千臺的計算機在同時執行我們的系統,在實際執行過程中,我們發現有時候我們寫入的檔案會出現全0或者部分全0的情況,但程式中可以肯定的是我們已經關閉了檔案控制代碼。這個問題困擾了我很久。它的發生概率大概在幾千分之一,而且大部分是出現在機器重啟時,也就是我們更新軟體後要求機器自動重啟,結果起來後發現更新的軟體中有部分檔案的大小是對的,但資料全是0。
首先考慮的是不是程式的bug,但分析下來,程式沒有任何問題,我們甚至用了 file.writeallbytes 這樣的靜態函式來寫檔案,依然會出現這個問題。
其次考慮的是不是磁碟快取造成?因為windows作業系統每個磁碟上都可以設定 enable write caching on the device. 如果開啟這個開關,寫入操作將先寫入磁碟的快取,然後在到達大小或時間門限時才寫入物理磁碟。如果內容還沒有完全寫入磁碟就重啟計算機,就會造成資料丟失。設定如下圖所示:
於是把這個功能取消,結果發現問題依舊。
這到底是怎麼回事?
放狗搜尋後發現 windows 除了在磁碟的硬體級別上可以提供快取外,在作業系統層面也有乙個檔案快取
這個功能叫 file caching, 是 windows 2000 以後提供的功能。如下圖所示:
當程序寫磁碟時,檔案會根據一定的策略快取到系統的檔案快取中,達到一定門限後才會寫入物理磁碟。由於這個系統檔案快取對應用程式是透明的,我們在應用程式中呼叫 檔案的 close, flush 只能保證檔案已經被寫入了作業系統的檔案快取,但無法保證檔案實際被寫入了磁碟。這個機制雖然提供了較好的寫入效能,但卻增加了丟失資料的風險。從應用角度,我們從邏輯上認為寫入已經成功,但實際上並沒有寫入到實際的磁碟,也就是說寫入是否真的成功了,軟體無從知道,這樣帶來很多邏輯上的混亂。特別是一些服務程序利用檔案鎖來控制多個程序鎖定的,比如 lucene.net, mongodb 等,就經常出現重啟後檔案鎖鎖定出問題的情況,估計也和這個機制的作用有關。
那麼這個機制的優點到底在**呢?
微軟提供這個機制當然是有原因的,他的最大優點是大大提高了讀取的效能。我們可以做如下的實驗:
當我們開啟乙個大檔案,並順序讀取這個檔案,我們發現系統開機後,第一次讀取的速度是非常慢的,這個速度主要取決於磁碟的讀取速度,因為第一次讀取是沒有快取的。但當我們關閉程序,再重新執行程序讀取這個大檔案時,無論是順序讀取還是隨機讀取,都比原來快上百倍,這就是因為這個作業系統快取在裡面起了作用,資料是從記憶體讀取的。由於這個快取是全域性的,程序退出後,檔案的快取並沒有被清空。我所做的開源全文索引專案 hubbledotnet 的較新版本就充分利用了這個機制,大大提高了機器重啟後首次讀取索引的速度。
那麼回到這個問題
我們有沒有辦法關閉這個檔案快取呢?答案是否定的。但幸運的是 windows 為應用提供了乙個標誌叫file_flag_write_through, 這個標誌可以讓應用在寫入快取的同時直接寫入磁碟。
用 c# 實現的**如下:
using (system.io.filestream fs = new filestream(filepath, filemode.create, fileaccess.write, fileshare.none,
8192, fileoptions.writethrough))
在程式中做了如上更改後,2000多台機器執行了半年,沒有發現一次檔案資料丟失的問題(原來幾乎每個月會出現幾次),基本可以證明這個機制有效。
1. 採用 writethrough 後沒有關閉磁碟快取,會不會造成資料丟失?
我們再看一下本文最上面的那個圖,磁碟快取有兩個 checkbox, 第乙個是是否開啟磁碟快取,第二個是是否關閉windows 的檔案寫快取重新整理磁碟。如果第二個選中,則有可能會出現資料丟失,如果沒有選中則不會。
預設設定,這個地方是不選中的。從實際測試來看,磁碟快取也確實不影響資料丟失的問題。
2. 採用 writethrough 後是否會降低寫入磁碟的效能。
我認為如果是隨機寫,可能是會有影響的,但如果是順序寫,filestream 這個類已經提供了快取功能,並不會有太大的影響。除非你直接呼叫windows 的檔案api去寫入檔案並且每次寫入檔案的內容都較小,這時確實會有影響。因為每次寫入都會觸發一次物理的磁碟寫。
檔案是否真的寫入了磁碟?
引用請註明出處 寫檔案後呼叫 filestream.close filestream.flush 或者 using filestream fs new filestream 檔案是否被實際寫入了磁碟?可能大多數人都會說肯定會寫入磁碟,但我要告訴你,不一定!我所在的公司有上千臺的計算機在同時執行我們的...
檔案真的被寫入了磁碟嗎?
寫檔案後呼叫 filestream.close filestream.flush 或者 using filestream fs new filestream 檔案是否被實際寫入了磁碟?可能大多數人都會說肯定會寫入磁碟,但我要告訴你,不一定!我所在的公司有上千臺的計算機在同時執行我們的系統,在實際執行...
文字檔案從磁碟讀取 寫入
using system using system.text using system.io namespace x.common return result 寫入文字檔案,按預設編碼 文字檔案路徑包括檔名 寫入內容 public static void write string filepath,...