如何防止PHP脚本多次运行?

ohh*_*hho 10 php linux cron centos

目前,我试图阻止onlytask.php脚本多次运行:

$fp = fopen("/tmp/"."onlyme.lock", "a+");
if (flock($fp, LOCK_EX | LOCK_NB)) {
  echo "task started\n";
  //
    while (true) {
      // do something lengthy
      sleep(10);
    }
  //
  flock($fp, LOCK_UN);
} else {
  echo "task already running\n";
}
fclose($fp);
Run Code Online (Sandbox Code Playgroud)

每分钟都有一个cron作业来执行上面的脚本:

* * * * * php /usr/local/src/onlytask.php
Run Code Online (Sandbox Code Playgroud)

它有效一段时间了.几天后,当我这样做时:

ps auxwww | grep onlytask
Run Code Online (Sandbox Code Playgroud)

我发现有两个实例正在运行!不是三个或更多,而不是一个.我杀死了其中一个实例.几天后,又有两个例子.

代码有什么问题?还有其他选择只限制onlytask.php运行的一个实例吗?

ps我的/tmp/文件夹没有清理干净.ls -al /tmp/*.lock显示锁定文件是在第一天创建的:

-rw-r--r--  1 root root    0 Dec  4 04:03 onlyme.lock
Run Code Online (Sandbox Code Playgroud)

gal*_*han 11

x打开锁定文件时应该使用标志:

<?php

$lock = '/tmp/myscript.lock';
$f = fopen($lock, 'x');
if ($f === false) {
  die("\nCan't acquire lock\n");
} else {
  // Do processing
  while (true) {
    echo "Working\n";
    sleep(2);
  }
  fclose($f);
  unlink($lock);
}
Run Code Online (Sandbox Code Playgroud)

请注意PHP手册

' x ' - 创建并仅供写作; 将文件指针放在文件的开头.如果文件已存在,则fopen()调用将失败,返回FALSE并生成级别为E_WARNING的错误.如果该文件不存在,请尝试创建它.这相当于为底层的open(2)系统调用指定O_EXCL | O_CREAT标志.

以下是手册页的O_EXCL解释:

O_EXCL - 如果设置了O_CREAT和O_EXCL,则如果文件存在,open()将失败.检查文件是否存在以及文件的创建(如果不存在)对于执行open()的其他线程来说应该是原子的,在设置了O_EXCL和O_CREAT的同一目录中命名相同的文件名.如果设置了O_EXCL和O_CREAT,并且路径名称为符号链接,则open()将失败并将errno设置为[EEXIST],而不管符号链接的内容如何.如果设置了O_EXCL且未设置O_CREAT,则结果未定义.

更新:

更可靠的方法 - 运行主脚本,获取锁,运行工作脚本并释放锁.

<?php
// File: main.php

$lock = '/tmp/myscript.lock';
$f = fopen($lock, 'x');
if ($f === false) {
  die("\nCan't acquire lock\n");
} else {
  // Spawn worker which does processing (redirect stderr to stdout)
  $worker = './worker 2>&1';
  $output = array();
  $retval = 0;
  exec($worker, $output, $retval);
  echo "Worker exited with code: $retval\n";
  echo "Output:\n";
  echo implode("\n", $output) . "\n";
  // Cleanup the lock
  fclose($f);
  unlink($lock);
}
Run Code Online (Sandbox Code Playgroud)

这是工人.让我们在其中引发一个假的致命错误:

#!/usr/bin/env php
<?php
// File: worker (must be executable +x)
for ($i = 0; $i < 3; $i++) {
  echo "Processing $i\n";
  if ($i == 2) {
    // Fake fatal error
    trigger_error("Oh, fatal error!", E_USER_ERROR);
  }
  sleep(1);
}
Run Code Online (Sandbox Code Playgroud)

这是我得到的输出:

galymzhan@atom:~$ php main.php 
Worker exited with code: 255
Output:
Processing 0
Processing 1
Processing 2
PHP Fatal error:  Oh, fatal error! in /home/galymzhan/worker on line 8
PHP Stack trace:
PHP   1. {main}() /home/galymzhan/worker:0
PHP   2. trigger_error() /home/galymzhan/worker:8
Run Code Online (Sandbox Code Playgroud)

重点是锁定文件已正确清理,因此您可以main.php再次运行而不会出现问题.

  • 如果代码在位置`echo"Working \n";`因故障而死,则.lock文件将保留在磁盘上而不被任何人清理,因此不再进行处理. (2认同)

ohh*_*hho 8

现在我检查进程是否正在运行ps并通过bash脚本扭曲php 脚本:

 #!/bin/bash

 PIDS=`ps aux | grep onlytask.php | grep -v grep`
 if [ -z "$PIDS" ]; then
     echo "Starting onlytask.php ..."
     php /usr/local/src/onlytask.php >> /var/log/onlytask.log &
 else
     echo "onlytask.php already running."
 fi
Run Code Online (Sandbox Code Playgroud)

并按分钟运行bash脚本cron.