2012-02-01 47 views
3

許多常見文件系統不提供原子操作,但在特定情況下以原子方式寫入文件非常重要。我試圖想出解決這個問題的辦法。在非事務性文件系統中實現原子文件寫入

我做出以下假設:

  • 在使用文件系統支持原子操作在inode的水平(例如NTFS)。這意味着移動刪除是原子。
  • 只有程序本身訪問文件。
  • 程序一次只有一個實例,它以單線程方式運行。
  • 爲簡單起見,每次寫入整個文件內容(即截斷 - 寫入)。

這會導致以下問題:寫入文件時,程序可能會被中斷,文件只剩下一部分內容來寫入。

我提出以下過程:

  1. 寫新的內容到一個臨時文件
  2. 移動原始文件原始到一個臨時位置備份
  3. 移動原創
  4. 刪除備份

備份文件是從原始文件區分(例如,它們可以被不同的前綴,或者可能是在同一個捲上一個單獨的目錄)。同時,他們的名字應直接映射到相應的原始(例如通過簡單地使用相同的文件名)。

但是,這並不會使操作原子化。該過程可以被中斷的步驟1,2,3或4:

  1. 葉一個不完整的潛在的新
  2. 移動是原子的,但目標文件現在丟失。 備份存在和完成。
  3. 移動是原子的,但有一個未使用的備份原始被替換爲內容
  4. 刪除是原子性的。

使用前面的假設2和3,程序必須在崩潰後重新啓動。在啓動過程中,應該執行這些恢復檢查:

  • 如果存在,但備份不,我們還是第1步後墜毀刪除,因爲它可能是不完整的。
  • 如果存在,備份確實太少,我們墜毀步驟2後,繼續執行步驟3.
  • 如果備份存在沒有,我們也墜毀後繼續執行步驟3與步驟4.

恢復過程本身,只使用原子操作,將簡單地繼續它中斷後停止的地方。

我相信這個想法可以確保單個程序的原子寫入。這些問題仍然存在:

  • 當使用同一程序的多個實例時,恢復過程與當前正在進行的其他程序中的文件寫入存在干擾。
  • 僅讀取但從不寫入的外部程序通常會得到正確的結果,但如果在請求的條目上同時存在寫入操作,則它們可能會錯誤地未找到條目。

可以通過使用策略(例如,檢查其他實例,並拒絕其他用戶的目錄訪問)來解決這些問題(以前被假設排除)。

最後,我的問題:這是否有道理,或者是否有缺陷?是否有任何問題阻止這種方法在實踐中被使用?

+0

我爲此寫了一些C#代碼,我可以根據要求添加它,但問題已經有一英里長了。 – mafu 2012-02-01 13:17:51

回答

3

你的步驟可以進一步簡化:

  1. 寫新的內容到一個臨時文件新
  2. 刪除原始文件
  3. 喬遷到原始

在啓動時:

  1. 如果Original不存在但Ne w,過程在步驟3之前中斷,將New移動到Original。
  2. 如果Original和New都存在,則在第2步之前,進程被中斷,刪除New。

我已經用它來管理配置文件,並且從未遇到過這個過程中的問題。

+0

-facepalm-你說得對 – mafu 2012-02-01 13:25:06

+1

你認爲你所指定的動作才能發生。並非每個文件系統都能保證。 – CodesInChaos 2012-04-24 16:47:15

+0

@CodeInChaos:這只是邪惡:(所以我要衝洗步驟之間的FS緩衝區,並繼續你的想法,我聽說還挺忽略清空請求... – mafu 2012-04-26 10:22:34

4

只有一件事你應該承擔,重命名文件是原子操作

所以下面的步驟將確保校正(至少在unix操作系統)

  1. 寫新的內容到一個臨時文件新
  2. 時重新啓動它的臨時文件重命名爲原來的名稱

這樣,如果應用程序崩潰或者使用舊的內容或新內容不需要額外的代碼。

+0

這是不行的,因爲舊的文件中有硬盤?要刪除,以使重命名 – mafu 2012-02-01 13:27:58

+0

@mafutrct:是不正確的;重命名可以覆蓋(至少在Unix) – 2012-08-18 23:05:39

+0

警告:一些文件系統不保證數據塊的元數據被提交之前寫入磁盤在這種情況下。 ,在寫入臨時文件之後但在重命名之前,您可能不得不調用'fdatasync()'或等價物。 – 2014-03-09 22:59:38