【发布时间】:2012-02-01 13:11:42
【问题描述】:
许多常见的文件系统不提供原子操作,但在某些场景中以原子方式写入文件非常重要。我试图想出一个解决这个问题的方法。
我做了以下假设:
- 正在使用的文件系统支持 inode 级别的原子操作(例如 NTFS)。这意味着 move 和 delete 是原子的。
- 只有程序本身才能访问文件。
- 一次只有一个程序实例,它以单线程方式运行。
- 为简单起见,每次都写入整个文件内容(即 truncate-write)。
这会留下以下问题:在写入文件时,程序可能会被中断,文件将只剩下部分内容要写入。
我提出以下流程:
- 将新内容写入临时文件新建
- 将原始文件Original移动到临时位置备份
- 将新移至原版
- 删除备份
New 和 Backup 文件与 Original 文件有区别(例如,它们的前缀可能不同,或者可能位于单独的目录中在同一卷上)。同时,它们的名称应该直接映射到相应的Original(例如通过简单地使用相同的文件名)。
然而,这并没有使操作原子化。该过程可能会被第 1、2、3 或 4 步中断:
- 留下一个可能不完整的新。
- 移动是原子的,但现在缺少目标文件。 新建和备份都存在并且是完整的。
- 移动是原子的,但有一个未使用的备份。 原来的被新的内容替换了
- 删除是原子的。
使用前面的假设 2 和 3,程序必须在崩溃后重新启动。在启动过程中,它应该执行这些恢复检查:
- 如果 New 存在但 Backup 不存在,我们在步骤 1 中或之后崩溃。删除 New,因为它可能不完整。李>
- 如果 New 存在并且 Backup 也存在,我们会在第 2 步后崩溃。继续第 3 步。
- 如果 Backup 存在但 New 也不存在,我们在第 3 步后崩溃。继续第 4 步。
恢复过程本身,仅使用原子操作,将在中断后继续从中断处继续。
我相信这个想法可以确保单个程序的原子写入。这些问题仍然存在:
- 使用同一程序的多个实例时,恢复过程会干扰其他程序中当前正在进行的文件写入。
- 只读不写的外部程序通常会得到正确的结果,但如果同时对请求的条目进行写操作,则可能会错误地找不到条目。
这些问题(之前的假设排除了)可以通过使用策略来解决(例如,检查其他实例,并拒绝其他用户访问目录)。
最后,我的问题是:这是否有意义,或者过程中是否存在缺陷?是否有任何问题阻碍了这种方法在实践中的使用?
【问题讨论】:
-
我为此写了一些 C# 代码,我可以根据要求添加它,但问题已经一英里长了。
标签: file-io transactions filesystems atomic