在非事务性文件系统中实现原子文件写入

时间:2012-02-01 13:11:43

标签: file-io transactions filesystems atomicity

许多常见的文件系统不提供原子操作,但在某些情况下以原子方式编写文件非常重要。我试图找到解决这个问题的方法。

我做了以下假设:

  • 正在使用的文件系统支持inode级别的原子操作(例如,NTFS)。这意味着 move delete 是原子的。
  • 只有程序本身才能访问这些文件。
  • 一次只有一个程序实例,它以单线程方式运行。
  • 为简单起见,每次都会写入整个文件内容(即截断写入)。

这会产生以下问题:在编写文件时,程序可能会被中断,文件只剩下要写入的部分内容。

我建议采用以下流程:

  1. 将新内容写入临时文件
  2. 将原始文件原始移至临时位置备份
  3. 移至原始
  4. 删除备份
  5. 备份文件可与原始文件区分开(例如,它们可以采用不同的前缀,也可以位于单独的目录中在同一卷上)。同时,他们的名字应该直接映射到相应的 Original (例如,只需使用相同的文件名)。

    然而,这并不会使操作成为原子。该过程可以中断步骤1,2,3或4:

    1. 留下可能不完整的
    2. 移动是原子的,但目标文件现在已丢失。 备份都存在并且已完成。
    3. 移动是原子的,但有一个未使用的备份原始已被内容
    4. 取代
    5. 删除是原子的。
    6. 使用之前的假设2和3,程序必须在崩溃后重新启动。在启动过程中,它应该执行这些恢复检查:

      • 如果存在,但备份没有,我们在步骤1中或之后崩溃。删除,因为它可能不完整。
      • 如果存在且备份也存在,我们在第2步后崩溃。继续执行第3步。
      • 如果备份存在,但也不存在,我们在步骤3之后崩溃了。继续执行步骤4.

      恢复过程本身只使用原子操作,只会在中断后从中断处继续。

      我相信这个想法确保了单个程序的原子写入。这些问题仍然存在:

      • 当使用同一程序的多个实例时,恢复过程会干扰其他程序中当前正在进行的文件写入。
      • 仅读取但从不写入的外部程序通常会得到正确的结果,但如果同时对请求的条目进行写入操作,则可能错误地找不到条目。

      这些问题(以前的假设排除在外)可以通过使用策略解决(例如,检查其他实例,并拒绝对其他用户的目录访问)。

      最后,我的问题:这是否有意义,或者是否存在缺陷?是否有任何问题妨碍这种方法在实践中使用?

2 个答案:

答案 0 :(得分:4)

您应该假设只有一件事,重命名文件是原子操作

因此,以下步骤将确保更正(至少在像OS这样的unix上)

  1. 将新内容写入临时文件新
  2. 将临时文件重命名为原始名称
  3. 这样,如果应用程序在重新启动时崩溃,则无需额外代码即可获取旧内容或新内容。

答案 1 :(得分:3)

您的步骤可以进一步简化:

  1. 将新内容写入临时文件新
  2. 删除原始文件
  3. 将新动作移至原始
  4. 启动时:

    1. 如果Original不存在但New确实存在,则在步骤3之前中断该过程,将New移至Original。
    2. 如果同时存在Original和New,则在步骤2之前中断该过程,删除New。
    3. 我已经在管理配置文件时使用过它,并且从未遇到过这个过程中的问题。