通过临时文件实现安全的/原子性的更新文件

前言

所谓的原子性更新文件指的是要么更新成功,要么更新失败,不能有中间状态,不能出现更新到一半失败了的情况。

想像一下,当更新文件的时候程序中途崩溃了或者被终止了出现文件更新不完整的情况或在更新的时候有另一个程序正好在读取该文件,此时就会出现非预期的情况:读取程序读取到了脏数据,同时原来的旧数据也丢失了。所以我们在更新一些比较重要的文件时要考虑实现原子性的更新。下面就来讲一下一种实现原子性更新文件的方法。

借助临时文件实现原子性更新文件

比较常见的实现原子性更新文件的方法是借助临时文件来实现:

  1. 把新数据写入到一个临时文件中
  2. 确保内容已经全部写入到磁盘上
  3. 用临时文件替换原来的文件

伪代码如下:

with open(tmp_file, 'wb') as fp:
    fp.write(new_content)
    fp.flush()
    fsync(fp_fd)

rename(tmp_file, orig_file)
fsync(orig_file_dir_fd)
close(orig_file_dir_fd)

使用第三方模块来实现

实现这个功能的第三方 Python 模块还是有一些的,我目前使用的是 https://github.com/untitaker/python-atomicwrites 这个模块:

$ pip install atomicwrites
>>> from atomicwrites import atomic_write
>>> with atomic_write('foo.txt', overwrite=True) as fp:
...     fp.write('Hello world.')
...

其他的模块可以在 https://github.comhttps://pypi.org 上搜索 atomic file 。然后看一下搜索出来的项目的代码,看看有没有实现上面的流程,以及一些细节的功能是否满足你的实际需求,以此来判断是使用该模块。

局限性

这种方法也有一定的局限性,常见的问题如下:

  • 有些文件系统不支持 fsync(file_fd) 操作
  • 有些文件系统不支持 fsync(dir_fd) 操作
  • windows/mac 可能不支持这些操作,需要找额外替代的方法去实现类似的功能

问题

同时这种方法还有一些缺陷:

  • fsync 操作在某些场景下有性能问题比不加 fsync 操作会慢很多,比如需要在短时间内更新大量文件的时候 (最近测试有个程序一个操作周期需要更新 900 多个文件,没加 fsync 比加了 fsync 的程序快了将近 8 倍)。

如果速度是不可妥协的需求并且可以接受一定概率下文件内容不完整的情况的话(可以再在文件内容中增加校验信息,或者在其他地方保存这个校验信息,读取文件内容的时候通过校验信息来检查文件的完整信息,读取到不完整的文件时直接抛异常),可以考虑省略 fsync , 但是如果文件不完整是绝对不可接受的情况的话,那就绝对不要省略 fsync [1]

总结

本文讲了一种常用的使用临时文件来实现原子性/安全更新文件的方法,希望能对你有所帮忙。 如果有其他更好的方法欢迎留言交流。


Comments