如何在NFS上进行适当的文件锁定



我正在尝试在Python 3X和Linux/MacOS中实现"记录管理器"类。该类相对简单明了,我想要的唯一"硬"的事情就是能够在多个进程上访问相同的文件(保存结果(。

从概念上讲,这似乎很容易:保存时,在文件上获取独家锁定。更新您的信息,保存新信息,在文件上发布独家锁定。足够容易。

我正在使用fcntl.lockf(file, fcntl.LOCK_EX)获取独家锁。问题是,在互联网上,我发现不同网站的都在说这是不可靠的,它在Windows上不起作用,NFS上的支持是摇摇欲坠的,并且MacOS和Linux之间的情况可能会发生变化。

我已经接受了该代码在Windows上不起作用,但是我希望能够使其在MACOS(单机器(和Linux上(在带有NFS的多个服务器上(上使用。

问题是我似乎无法完成这项工作。经过一段时间的调试,测试通过了MacOS之后,一旦我用Linux在NFS上尝试了它们(Ubuntu 16.04(,他们就失败了。问题是多个过程保存的信息之间的不一致性 - 某些过程缺少其修改,这意味着锁定和保存过程中出现了问题。

我确定有我做错了什么,我怀疑这可能与我在线阅读的问题有关。那么,多次访问对MACOS和Linux在NFS上使用的同一文件的正确方法是什么?

编辑

这是将新信息写入磁盘的典型方法看起来像:

sf = open(self._save_file_path, 'rb+')
try:
    fcntl.lockf(sf, fcntl.LOCK_EX)  # acquire an exclusive lock - only one writer
    self._raw_update(sf) #updates the records from file (other processes may have modified it)
    self._saved_records[name] = new_info
    self._raw_save() #does not check for locks (but does *not* release the lock on self._save_file_path)
finally:
    sf.flush()
    os.fsync(sf.fileno()) #forcing the OS to write to disk
    sf.close() #release the lock and close

这是只有的典型方法,看起来像:

sf = open(self._save_file_path, 'rb')
try:
    fcntl.lockf(sf, fcntl.LOCK_SH)  # acquire shared lock - multiple writers
    self._raw_update(sf) #updates the records from file (other processes may have modified it)
    return self._saved_records
finally:
    sf.close() #release the lock and close

另外,这就是_raw_save的样子:

def _raw_save(self):
    #write to temp file first to avoid accidental corruption of information. 
    #os.replace is guaranteed to be an atomic operation in POSIX
    with open('temp_file', 'wb') as p:
        p.write(self._saved_records)
    os.replace('temp_file', self._save_file_path) #pretty sure this does not release the lock

错误消息

我已经编写了一个单元测试,其中我创建了100个不同的过程,50个读取和50个写入同一文件的过程。每个过程都会进行一些随机等待,以避免依次访问文件。

问题是某些记录没有保留;最后,有大约3-4个随机记录,所以我最终只有46-47记录,而不是50。

编辑2

我已经修改了上面的代码,而不是在文件本身上而是在单独的锁定文件上获取锁。这样可以防止关闭文件将发布锁定的问题(如@JanneB所建议(,并使代码在Mac上正确工作。不过,与NFS的Linux失败了相同的代码。

我看不出文件锁和os.replace((的组合如何有意义。替换文件(即更换目录条目(时,所有现有的文件锁(可能包括等待锁定成功的文件锁,我不确定这里的语义(,文件描述符将违反旧文件,而不是新文件。我怀疑这是比赛条件背后的原因导致您丢失测试中的一些记录。

os.replace((是一项很好的技术,可以确保读者不阅读部分更新。但是,面对多个更新程序,它并不能坚固(除非丢失一些更新是可以的(。

另一个问题是FCNTL是一个非常愚蠢的API。特别是,锁与过程绑定,而不是文件描述符。这意味着例如指向文件的任何文件描述符上的关闭((将释放锁定。

一种方法是使用"锁定文件",例如利用链接的原子量((。来自http://man7.org/linux/man-pages/man2/open.2.html:

便携式 想要使用一个执行原子文件锁定的程序 Lockfile,需要避免依赖NFS支持 o_excl,可以在同一文件系统上创建一个唯一的文件(例如, 并入主机名和pid(,并使用链接(2(制作 链接到Lockfile。如果链接(2(返回0,则锁定为 成功的。否则,请在唯一文件上使用stat(2( 检查其链接计数是否增加到2,在这种情况下 锁也成功了。

如果可以读取稍微过时的数据,则可以仅适用于更新文件时使用的临时文件,然后使用os.replace((您用于阅读的"主"文件(阅读(然后可以无锁(。如果不是,那么您需要为"主"文件执行link((技巧而忘记共享/独家锁定,所有锁均为独家。

Addendum :使用锁定文件时要处理的一件棘手的事情是当过程出于何种原因死亡时该怎么办,并将锁定文件留在周围。如果这是无人值守的,您可能需要将某种超时和删除锁定文件(例如检查stat((时间戳(。

使用随机命名的硬链接,链接对这些文件作为锁定文件是一种常见的策略(例如,这是一个常见的策略,并且比使用lockd更好,但是有关NFS上各种锁的限制的更多信息,请阅读以下内容:http:http:http:http:http:http:http:http:http:http:http://0pointer.de/blog/projects/locking.html

您还会发现,使用NFS上的Mbox文件,这是MTA软件的长期标准问题。最好的答案可能是使用Maildir而不是Mbox,但是如果您在PostFix之类的源代码中寻找示例,则可以接近最佳实践。而且,如果他们根本不解决这个问题,那也可能是您的答案。

nfs非常适合文件共享。它是一种"传输"介质。

我多次沿着NFS-for-for-tata-transmission路上沿着NFS-for-Data-transmission路上。在每种情况下,解决方案都涉及远离NFS。

获得可靠的锁定是问题的一部分。另一部分是服务器上文件的更新,并期望客户在某个特定时间点接收该数据(例如,他们可以抓住锁定(。

nfs并非设计为数据传输解决方案。涉及缓存和时机。更不用说文件内容的分页和文件元数据(例如,ATIME属性(。和客户端o/s'es在本地跟踪州(例如,在写入文件末尾时,要附加客户数据的"位置"(。

对于分布式,同步的商店,我建议您查看可以做到这一点的工具。例如Cassandra,甚至是通用数据库。

如果我要正确阅读用例,也可以使用简单的基于服务器的解决方案。让服务器收听TCP连接,从连接中读取消息,然后将每个连接读取以文件为文件,将服务器本身中的写入序列化。拥有自己的协议(知道消息的启动和停止的位置(还有一些额外的复杂性,但是否则,它相当直接。

相关内容

  • 没有找到相关文章

最新更新