在AWS EFS上使用flock模拟关键代码段是否安全?

7
根据文档,AWS EFS(Amazon Elastic File System)支持文件锁定:

Amazon EFS提供了文件系统接口和文件系统访问语义(例如强一致性和文件锁定)。

在本地文件系统(例如ext4)中,flock 可以在shell脚本中用于创建 临界区。例如,此答案描述了我过去使用过的一种模式:
#!/bin/bash
(
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200 || exit 1

  # Do stuff

) 200>/var/lock/.myscript.exclusivelock

同样的模式能够应用在EFS上吗?亚马逊提到他们使用了NFSv4协议,但是它是否提供与ext4上的flock相同的保证呢?

如果不能,那么如何确保一个操作在连接到同一EFS卷的所有EC2实例中独占运行?如果它适用于进程,则已足够,因为我不打算运行多个线程。

或者我误解了NFSv4提供的锁定支持吗?不幸的是,我不知道协议的细节,但在分布式系统中提供原子性比在本地机器上更困难。

更新:小规模实验

当然不是证明,但在我的测试中,它可以在多个实例之间工作。目前,我认为这种模式是安全的。但仍然希望知道它在理论上是否可靠。


我也在自己测试这个。到目前为止,我还没有发现任何问题。 - Scott Talbert
@ScottTalbert 是的,我正在考虑自己写一个答案。据我所知,上面的模式应该得到NFS的支持,而亚马逊的EFS实现了这一点。到目前为止,我也没有遇到任何问题。 - Philipp Claßen
2个回答

5

它应该可以工作。

在问题中使用的模式中,flock命令应该适用于所有NFS文件系统。这意味着它也适用于实现NFSv4协议的EFS。在实践中,当我使用它在不同的EC2实例上同步shell脚本时,我也没有遇到任何问题。


根据您的用例,您必须了解Linux文件锁定的陷阱,尽管大部分不是针对NFS的。例如,上面的模式在进程级别上运行,如果要同步多个线程,则无法使用它。

在阅读过程中,我遇到了旧问题。在2.6.12之前的内核中,似乎存在NFS和flock系统调用的问题(例如,请参见flock vs lockf on Linux)。

这里不应该适用,因为在新内核中已经得到改善。查看flock命令的源代码,您可以确认它仍然使用flock系统调用,但它可能被安全的fcntl系统调用所实现:

while (flock(fd, type | block)) {
  ...
  case EBADF:       /* since Linux 3.4 (commit 55725513) */
        /* Probably NFSv4 where flock() is emulated by fcntl().
         * Let's try to reopen in read-write mode.
         */

注意:这个解决方案是指在Linux内核的此提交中可以找到:
由于我们可能使用NFS字节范围锁来模拟flock()锁,所以不能依赖VFS为我们检查文件打开模式。

5
很不幸,我可能刚刚找到一个这种方法不起作用的案例。我在周末离开了两个EC2实例,一个持有一个群集,另一个等待获取它。似乎在某个时候,持有群集的机器失去了与NFS服务器的连接(131090毫秒。 12月2日05:04:32 ip-172-31-30-242内核:[105059.038603] nfs:服务器fs-e05dfc48 .efs.us-west-2.amazonaws.com未响应,超时)。此时,另一个系统似乎能够获取锁,而原始系统仍然认为它拥有锁。两个系统都带着锁。 - Scott Talbert
@ScottTalbert 很有趣。在我的设置中,我使用带有200秒超时的flock。大多数锁定时间很短(<1秒)。我没有使用它来同步长时间运行的批处理作业。 - Philipp Claßen
2
是的,在我的情况下,我试图使用它来防止多个节点扮演“领导者”角色,并持续很长时间。但似乎它不适用于这种情况,因为NFS可以收回锁定,就像我看到的一种情况,其中一个节点暂时失去与服务器的连接。 - Scott Talbert

-2
如果使用情况只是为了确保另一个进程(或实例/容器)不会“接管”工作,我会使用一个更简单的锁文件。它被称为锁文件,但实际上只是一个简单的文件。
类似这样的东西
while true; do
    printf "Aquiring lock: "
    if [ ! -e "some_lock_file_somewhere" ]; then
        echo "done."
        touch some_lock_file_somewhere
        echo "doing stuff"
        sleep 60 # just because I couldn't come up with something that takes a while :D 
    else
        echo "waiting 60s for lock"
        sleep 60
    fi
done

你需要手动删除那个锁定文件,或者编写逻辑来完成这个任务,但是这个逻辑可以在多个终端上运行,只有第一个终端会执行实际的操作。

这对我来说看起来像是一场竞赛,因为它不是一个原子的测试和设置。如果你同时运行两个脚本,可能会出现两者都没有首先看到文件,最终都执行该代码块的情况。对于某些使用情况来说,这可能是可以接受的,但这不符合我对临界区的理解(临界区应该提供独占访问)。 - undefined
这对我来说看起来像是一场竞赛,因为它不是原子测试和设置。如果您同时运行两个脚本,可能会出现两者都无法首先看到文件并最终执行该块的情况。对于某些用例来说,这可能是可以接受的,但这不符合我对临界区的理解(应该提供独占访问)。 - Philipp Claßen
是的,那是真的。但是发生这种情况的可能性非常小。如果你想要最小化发生这种情况的风险,如果你有多个实例同时出现(例如在创建基础设施时触发X个实例从ASG),那么我会在该部分前面加入一个随机的(1到10秒之间?)延迟。顺便说一下,即使使用flock,这种情况仍然可能发生! - undefined
没错,那是真的。但这种情况发生的可能性非常小。如果你想减少发生这种情况的风险,如果有多个实例同时出现(例如在创建基础设施时触发 X 个实例),那么我建议在该部分之前加入一个随机睡眠(1 到 10 秒之间的睡眠时间?)。顺便说一下,即使使用 flock 也有可能发生这种情况! - Fransurbo
哇,这太可怕了,风险一点都不小,特别是在远程文件系统上,从创建到在其他节点上可见存在延迟。而且,如果正确使用flock(),是不会发生这种情况的。 - undefined

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