原子文件写操作(跨平台)

32

如何构建一个原子文件写操作?这个文件将由一个Java服务写入,被Python脚本读取。
需要说明的是,读取的次数要远大于写入。但是写入会批量进行且通常会很长。文件大小为兆字节。

目前我的方法是:

  • 将文件内容写入同一目录下的临时文件中
  • 删除旧文件
  • 将临时文件重命名为旧文件名

这种方法正确吗?如何避免旧文件被删除但新文件名尚未重命名的情况?

这些编程语言(Python 和 Java)是否提供锁定机制以避免这种情况?


2
顺便提一下:看起来Java NIO.2对此有更好的支持(至少在API级别上):http://openjdk.java.net/projects/nio/javadoc/java/nio/file/Path.html#moveTo%28java.nio.file.Path,%20java.nio.file.CopyOption...%29 - Pascal Thivent
类似于https://dev59.com/6XI-5IYBdhLWcg3whYsr - Quintin Par
@Pascal。它是Java 7编写的。还有很长的路要走... :-( - Quintin Par
7个回答

14
据我所知,不行。原因是要实现这样的原子操作,需要有操作系统支持事务文件系统,而目前主流的操作系统都没有提供事务文件系统。
编辑-至少对于符合POSIX标准的系统来说,我的说法是错误的。POSIX的rename系统调用将在目标名称已存在的情况下执行原子替换...正如@janneb指出的那样。这应该足以以原子方式执行OP的操作。
但是,事实仍然是Java的File.renameTo()方法明确地不能保证是原子的,因此它不提供跨平台解决方案来解决OP的问题。
第二次编辑 - 使用Java 7,您可以使用CopyOption和ATOMIC_MOVE选项与java.nio.file.Files.move(Path源,Path目标,CopyOption ... options)方法。如果不支持此选项(由操作系统/文件系统),则应该会抛出异常。

2
啊,那些麻烦的操作系统差异阻止了“跨平台”的解决方案。 - S.Lott
一个真正的答案是应该使用文件锁。 - unixman83
“而且没有任何主流操作系统提供事务性文件系统。”什么?NTFS很长时间以来就有事务支持了。http://msdn.microsoft.com/en-us/magazine/cc163388.aspx - Ark-kun
@unixman83 在写入数据到新文件时你可以锁定它,但如何锁定一个目录来进行原子交换呢? :) - Hakanai
@Ark-kun - 我不知道这一点。然而,它实际上并没有帮助,因为大多数文件系统都不支持该功能,并且它也没有通过标准的Java API公开。 - Stephen C

7

至少在 POSIX 平台上,省略第三步(删除旧文件)。在 POSIX 中,文件系统内的重命名操作保证是原子性的,将一个文件重命名到已存在的文件名时会原子性地替换它。


3

这是一个典型的生产者/消费者问题。您可以通过使用文件重命名来解决此问题,在POSIX系统上具有原子性。


2
在Linux、Solaris和Unix中,这很容易。只需从您的程序中使用rename()或mv即可。文件需要在同一文件系统上。
在Windows上,如果您可以控制两个程序,则可以实现此功能。使用LockFileEx。对于读取,打开锁文件上的共享锁。对于写入,打开锁文件上的独占锁。在Windows中,锁定是奇怪的,因此我建议为此使用单独的锁定文件

1

1
是的...但这样做不能让你进行原子文件替换。 - Stephen C
1
你的想法并不完全正确。 使用这个API,你可以创建“.lock”文件,并将其用作信号量。 用例:如果文件被锁定 - Python会等待直到它被解锁,然后开始读取(并锁定文件),读取完成后解锁文件。当服务需要写入数据时 - 检查文件是否被锁定,等待直到它变为未锁定状态,锁定它,写入数据,然后解锁它。 - St.Shadow
无论如何,这实际上是一个仅包含链接的答案,而且该链接并没有解释如何解决OP的问题。按照今天的标准,它是一个低质量的答案。 - Stephen C
重要的文档部分:“锁定区域是否实际防止其他程序访问锁定区域的内容取决于系统,因此未指定。” - kapex
@St.Shadow,锁定可以防止“正在正确运行的程序的另一个副本”同时更改内容,但这并不意味着操作系统崩溃、JVM错误等不会导致文件部分写入或其他损坏(而适当的原子创建和重命名工作流程则可以避免这种情况)。 - Charles Duffy

1
你可以尝试使用额外的文件作为锁,但我不确定这是否能正常工作。(这将强制你在Java和Python两侧创建锁检查和重试逻辑。)
另一个解决方案是根本不创建文件,也许你可以让你的Java进程监听一个端口并从那里提供数据,而不是从文件中提供?

1

在Python脚本中,让它们向服务请求权限。当服务正在写入时,会对文件进行锁定。如果存在锁定,则服务会拒绝Python的请求。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接