跟进:How to safely update a file that has many readers and one writer?
在我之前的问题中,我发现你可以使用FileChannel的锁来确保读写顺序。
但是,如果编写器在写入失败时(例如JVM崩溃),你如何处理这种情况?这个基本算法看起来像,
WRITER:
lock file
write file
release file
READER:
lock file
read file
release file
如果JVM在write file
期间崩溃,请确保锁定将被释放,但现在我的文件不完整。我希望完整的东西总是可读的。要么旧内容新内容,要么介于两者之间。
我的第一个策略是写入临时文件,然后将内容复制到“实时”文件中(同时确保良好的锁定)。算法是,
WRITER:
lock temp file
write temp file
lock file
copy temp to file
release file
release temp
delete temp
READER:
lock file
read file
release file
一个好处是delete temp
如果已被另一位作家锁定,则不会删除临时文件。
但是如果JVM在copy temp to file
期间崩溃,那么该算法无法处理。所以我添加了一个copying
标志,
WRITER:
lock temp file
write temp file
lock file
create copying flag
copy temp to file
delete copying flag
release file
release temp
delete temp
READER:
lock file
if copying flag exists
copy temp to file
delete copying flag
delete temp
end
read file
release file
访问copying
文件时不会有两件事情,因为它受到文件锁的保护。
现在,这是这样做的吗?确保非常简单的事情似乎非常复杂。是否有一些Java库可以为我处理这个问题?
修改
好吧,我设法在第三次尝试中弄错了。当copy temp to file
WRITER:
lock file
write temp file
create copying flag
copy temp to file
delete copying flag
delete temp
release file
READER:
lock file
if copying flag exists
copy temp to file
delete copying flag
delete temp
end
read file
release file
时,读者不会锁定临时值。它也不是简单的锁定临时文件的简单修复!这将导致作者和读者以不同的顺序获取锁,并可能导致死锁。这一直变得越来越复杂。这是我的第四次尝试,
WRITER:
lock
write temp file
create copying flag
copy temp to file
delete copying flag
delete temp
release
READER:
lock
if copying flag exists
copy temp to file
delete copying flag
delete temp
end
read file
release
这次临时文件由主锁保护,因此它甚至不需要自己的锁。
编辑2
当我说JVM崩溃时,我的意思是说电源耗尽而你没有UPS。
编辑3
我仍然设法犯了另一个错误。您不应该锁定您正在写入或读取的文件。这会导致问题,因为除非在Java中使用RandomAccessFile,否则无法同时获得读取和写入锁定,而Java中没有实现输入/输出流。
您要做的只是锁定锁定文件,以保护您正在读取或写入的文件。这是更新的算法:
{{1}}
锁定和释放保护文件,临时文件和复制标志。唯一的问题是现在读者锁不能共享,但它永远不会真的。读者总是有机会修改文件,因此首先制作可共享锁是错误的。
答案 0 :(得分:1)
尽管没有防弹,跨操作系统,跨FS的解决方案,但“写入独特的临时文件并重命名”策略仍然是您的最佳选择。大多数平台/文件系统都试图使文件重命名(有效)原子化。请注意,您要使用+单独的+锁定文件进行锁定。
所以,假设您要更新“myfile.txt”:
File.createTempFile()
)FileChannel.force(true)
)之前fsync临时文件。在某些平台(windows)上,由于文件操作的限制,你需要多做一些跳舞(你可以在重命名之前将“myfile.txt”移动到“myfile.txt.old”,然后使用“.old”如果你需要在阅读时恢复文件。)
答案 1 :(得分:0)
我认为没有一个完美的答案。我不知道你需要做什么,但你可以写一个新文件,然后成功,重命名文件,而不是复制。重命名很快,因此不应该更容易发生崩溃。如果在重命名阶段失败,这仍然无济于事,但您已将风险窗口降至最低。
同样,我不确定它是否适用于您的需求,但是您是否可以在文件末尾写一些文件块来显示所有已写入的数据?
答案 2 :(得分:0)
我假设你有一个大文件,你不断追加。 VM的崩溃不会经常发生。但如果它们发生,您需要一种方法来回滚失败的更改。你只需要一种方法来知道回滚多远。例如,将最后一个文件长度写入新文件:
WRITER:
lock file
write file position to pos-file
write file
remove pos-file
unlock file
如果作者崩溃,你的一个读者将获得读锁定。他们必须检查pos文件。如果他们发现一个发生了崩溃。如果他们查看文件内部,他们知道回滚更改的距离,则会再次获得一致的文件。当然,回滚过程必须以与写过程类似的方式进行。
如果您没有追加但替换文件,则可以使用相同的方法:
WRITER:
lock file
write writing-in-progress-file
write file
remove writing-in-progress-file
unlock file
与之前适用于读者的规则相同。当正在写入文件存在但读取器已获取读锁定时,写入的文件处于不一致状态。
答案 3 :(得分:0)
如果没有某些操作系统支持,就不可能将任意复杂的文件操作设置为原子操作,而不需要打开文件的程序来完成或回滚可能正在进行的操作。如果这个警告是可以接受的,可以采取以下措施:
如果写入文件的原始操作在写入更新记录位置之前失败,则将有效地忽略所有写入操作。如果在写入更新记录位置之后但在清除之前它失败,则在下次打开文件时,将提交所有写入操作(其中一些可能已经执行,但是再次执行它们应该是无害的) 。如果在写入更新记录位置后失败,则文件更新将完成,失败根本不会影响它。
其他一些方法使用单独的文件来保存挂起的写入。在某些情况下,这可能是一个好主意,但它的缺点是将文件分成两部分,两部分必须保持在一起。仅复制一个文件,或意外配对在不同时间制作的两个文件的副本,可能会导致数据丢失或损坏。
答案 4 :(得分:0)
解决方案:使用2个文件并按连续顺序写入 只有1次写入可能会失败。