【问题标题】:Why are "lock" files used in PHP instead of just counting the processes?为什么在 PHP 中使用“锁定”文件而不是仅仅计算进程?
【发布时间】:2019-01-18 01:37:38
【问题描述】:

我见过很多使用“锁定”文件来跟踪 PHP 脚本当前是否正在运行的示例。

例子:

  1. 脚本开始
  2. 检查“/tmp/lockfile”当前是否被锁定
  3. 如果已锁定,请退出。如果没有,请锁定文件并继续

这样,如果一个长时间运行的脚本启动了两次,那么只有第一个实例会运行。太棒了。

但是,绕过它似乎是错误的方法。我们为什么不直接检查进程是否已经像这样运行?

if(exec("ps -C " . basename(__FILE__) . " --no-headers | wc -l") > 1){
  echo "Already running.";
  exit;
}

这种方法有什么潜在的缺陷吗?为什么我经常看到“锁定”文件解决方法?用我们正在寻找的名称来计算进程似乎更准确....

【问题讨论】:

  • 可能是因为在不了解 Bash 等知识的情况下,可以在纯 PHP 中检查锁定文件...
  • 取决于您的主机,您可能无法运行 exec
  • 我认为您可能会考虑编辑您的问题...这绝对适用于比 PHP 更广泛的一般实践。
  • 因为ps 只适用于linux,flock 也适用于windows
  • 是的,许多 Windows 服务器不支持 Bash

标签: php command-line-interface daemon


【解决方案1】:

根据这里的 cmets 和我自己的观察,我列出了两种方法的优缺点:

flock方法:

优点:

  • 跨操作系统更兼容
  • 不需要 bash 知识
  • 更常用的方法,大量示例
  • 即使在 exec() 禁用时也可以使用
  • 可以在单个文件中使用多个锁,以允许同一文件同时运行不同的“模式”

缺点:

  • 不确定。如果您的锁定文件被外部进程/用户删除,您最终可能会得到多个进程。如果您将锁定文件保存在 /tmp 目录中,这是一种有效的可能性,因为该目录中的所有内容都应该是“临时的”
  • 在某些情况下,当一个进程意外死掉时,文件锁可以转移到一个不相关的进程(我一开始不相信,但我发现它发生在 200 多个基于 unix 的系统中的实例(尽管很少) ,在 3 种不同的操作系统中)

exec("ps -C...")方法

优点:

  • 由于您实际上是在计算进程数,因此无论文件锁定等状态如何,它都会每次都有效。

缺点:

  • 仅适用于 linux
  • 需要启用“exec”
  • 如果您更改脚本名称,可能会导致双重处理(并确保您的脚本名称未在代码中硬编码)
  • 假设您的脚本只有一个正在运行的“模式”

编辑:我最终使用了这个:

if (exec("pgrep -x " . $scriptName . " -u ". $currentUser . " | wc -l") > 1)
{
  echo $scriptName . " is already running.\n";
  exit;
}

...因为ps 不允许您过滤除进程名称之外的进程所有者,并且我希望允许此脚本在其他用户运行时多次运行。


编辑 2:

...所以,在运行了几天之后,它也不完美。不知何故,该进程在同一用户下的同一台机器上启动了多次。我唯一的猜测是,有一些问题(内存不足等)导致 pgrep 什么都没有返回,而它本应该返回一些东西。

这意味着flock 方法和计数过程方法都不是 100% 可靠的。您必须确定哪种方法更适合您的项目。

最终,我使用了另一种解决方案,将当前任务的 PID 存储在“锁定”文件中,实际上并没有被锁定。然后,当脚本启动时,检查锁定文件是否存在,如果存在,则获取内容(脚本上次启动时的 PID)然后,通过比较 /proc/#PID#/cmdline 内容来检查它是否仍在运行使用正在运行的脚本的名称。

【讨论】:

  • 此 exec 方法的缺点:它假定脚本仅用于此功能。如果您的脚本有两个或更多功能,则可能是另一个正在运行。当然,人们可以争辩说每个函数都应该在自己的文件中。
【解决方案2】:

首先,命令不对。当我运行php test.php 和命令时

 ps -C test.php

将一无所获。可以使用ps -aux|grep 'test.php' -c获取进程号,但是exec("ps -aux|grep 'php test.php' -c");返回的号必须减2才是真正的进程号

使用锁文件的主要原因是exec或其他命令函数需要一些特殊权限,并且是php.ini默认配置中的disable_functions。

这样的测试脚本:

$count = exec("ps -aux|grep 'php test.php' -c");

if($count > 3){
  echo "Already running.".$count;
  exit;
}

while(1){
    sleep(20);
}

【讨论】:

  • 嗯,您的ps 可能有问题?根据manual-C 是“按命令名称选择”。对我来说,我的代码中的 bash 命令在进程运行时返回“1”。此外,-C 需要完全匹配,您的 grep 也会得到部分匹配,这可能会搞砸。
【解决方案3】:

使用“锁定文件”的主要原因仅仅是因为它们可以在任何主机环境中工作。如果你可以“创建一个文件”并且如果你可以“锁定它”,那么这样的代码就可以在......任何地方工作。

另外——“‘聪明’不会获得任何东西。”众所周知,这种众所周知的策略效果很好——因此,“顺其自然”。

【讨论】:

  • “锁定”文件方法对我来说非常有用——直到我开始在几个脚本的 200 多个位置使用它。我开始注意到发生了奇怪的事情(比如不相关的脚本(很少)“窃取”文件上的锁,阻止它正常工作。可能是文件锁的操作系统问题?)无论如何,我的脚本只能在 Linux 上运行(这不会t 改变)。因此,就我而言,无论文件锁定状态如何都绝对有效的解决方案比在任何主机环境中都有效的解决方案要好。
  • 在某些操作系统中存在“锁定文件”的严重问题,例如Unix/Linux 将锁视为建议性的。 因此,存在文件通常被用作锁定到位的指示。
  • 我以前见过“文件存在”的实现。我唯一关心的问题(如果我错了,请纠正我)是,如果进程遭受“硬”崩溃,我们将留下文件,并且脚本将不再能够自我恢复(因为它认为它仍在运行)
【解决方案4】:

使用锁定文件的最重要原因是锁定文件使用原子锁定机制,允许您运行多个实例脚本,在比脚本本身更高级别的上下文中,并且更安全

锁文件是原子的

遍历进程列表本质上容易出现竞争条件;在检索和遍历列表所需的时间中,可能刚刚产生了第二个进程,而您无意中最终得到了多个进程。

文件锁定机制是严格原子的。只有一个进程可以获得一个文件的独占锁,所以当它确实有锁时,同一个命令不可能运行两次。

锁定文件允许同一脚本的多个实例

假设您希望运行多个单独的脚本实例,但每个实例都有自己的范围/选项。如果您只是简单地计算脚本出现在 ps 输出中的次数,您将只能运行一个实例。通过使用单独的锁定文件,您可以在单个范围内正确锁定其运行状态。

锁定文件更安全

锁定文件只是一种更加优雅和安全的设计。您不必滥用进程列表(这需要更多权限,并且必须在每个操作系统上以不同方式访问和解析)来推断脚本是否已在运行,而是有一个专用系统来显式锁定状态。

总结

因此,重申一下使用锁文件而不是其他方法的所有原因:

  • 锁定文件允许原子锁定,否定竞争条件。
  • 它们允许运行同一脚本的多个实例/范围。
  • 文件系统访问权限比执行权限或对进程信息的完全访问权限更普遍可用
  • 它们更安全,因为不需要特定锁定文件之外的权限。
  • 使用文件锁更兼容不同的操作系统。
  • 这是一个更优雅的解决方案;不需要通过进程列表进行解析。

至于其他答案中提到的缺点;锁文件可以被删除,有时锁会转移到另一个进程:

通过设置适当的权限并将文件存储在非易失性存储中来防止删除锁定文件。如果按照规范使用,锁定文件是“确定的”,与进程列表不同。

确实,派生子进程的进程将“遗留”它锁定给仍在运行的任何子进程,但是一旦脚本完成,通过专门解锁文件很容易解决这个问题。例如。使用flock --unlock

长话短说:您应该始终使用锁定文件而不是解析正在运行的进程

其他解决方案

还有其他解决方案,但它们通常比简单的文件锁定没有任何好处,除非您有其他要求:

  • 独立数据库/存储(例如 redis)中的互斥锁。
  • 专门监听网络接口上的端口。

【讨论】:

    猜你喜欢
    • 2015-07-06
    • 2017-01-09
    • 2016-07-16
    • 1970-01-01
    • 2021-03-08
    • 1970-01-01
    • 2018-02-08
    • 2012-05-04
    • 2012-08-26
    相关资源
    最近更新 更多