【问题标题】:Dont run a cron php task until last one has finished在最后一个完成之前不要运行 cron php 任务
【发布时间】:2014-07-21 04:20:11
【问题描述】:

我有一个 php-cli 脚本,由 cron 每个 5 minutes 运行。因为这个间隔很短,所以multiple processes同时运行。这不是我想要的,因为这个脚本必须在文本文件中写入一个数字id,每次都会递增。碰巧writers同时在这个文本文件上写入,写入的值不正确。

我尝试使用 php 的 flock 函数来阻止写入文件,当另一个进程正在写入文件但它不起作用时。

$fw = fopen($path, 'r+');
if (flock($fw, LOCK_EX)) {
    ftruncate($fw, 0);
    fwrite($fw, $latestid);
    fflush($fw);
    flock($fw, LOCK_UN);
}
fclose($fw);

所以我想解决这个问题的方法是创建一个bash 脚本来验证是否有这个php script 的实例正在运行,如果是,它应该等到它完成。但是我不知道该怎么做,有什么想法吗?

【问题讨论】:

  • 脚本运行超过5分钟?
  • 明确一点,其他进程也在使用flock,对吗?
  • @etherous,是的,所有进程都运行相同的php script
  • @MarcellFülöp,是的,因为在 mysql 数据库中有插入,有时会使脚本运行超过 5 分钟。

标签: php linux bash concurrency cron


【解决方案1】:

我不太明白每 5 分钟递增一次计数器会导致多个进程同时尝试写入计数器文件,但是...

更简单的方法是使用类似于以下的简单锁定机制:

<?php

$lock_filename = 'nobodyshouldincrementthecounterwhenthisfileishere';

if(file_exists($lock_filename)) {
  return;
}

touch($lock_filename);

// your stuff...

unlink($lock_filename);

这是一种简单的方法,不会处理脚本在删除锁定文件之前中断的情况,在这种情况下,在删除之前它永远不会再次运行。

按照您的建议,也可以使用更复杂的方法,例如在自己的进程中 fork 作业,将 PID 写入文件,然后在运行作业之前检查该 PID 是否仍在运行。

【讨论】:

  • 当只有一个进程同时递增时,递增不是问题。但正如我所说,此脚本中还有其他操作使其运行超过 5 分钟(抓取和插入)。
  • 好吧,在这种情况下,如果您不希望这些进程中的任何一个跳过增量,理想的方法是当锁定文件存在时,而不是简单地return,将其保持在一个循环中sleeps 大约一秒钟,然后重新检查锁定文件。
  • 我不会让进程休眠,因为它会在 5 分钟后运行,我想我会使用 bash 锁 flock mutual exclusion
  • 使用文件作为标志或指示符以防止启动同一程序的下一个会话的解决方案并不可靠,因为如果锁定文件一次不会被删除(假设,出了问题),那么此解决方案将无法自动恢复此类临时损坏。为了使这种解决方案变得健壮,您需要添加对锁定文件年龄的主动监控。如果它的年龄超过一个合理的阈值,那么主动监控必须删除这个锁文件。
【解决方案2】:

为了防止任何程序的下一个会话开始,直到上一个会话仍在运行,例如下一个 cron 作业,我建议使用内置到您的程序中或外部检查该程序的运行进程。只需在程序启动前执行

 ps -ef|grep <process_name>|grep -v grep|wc -l

并检查其结果是否为 0。只有在这种情况下,您的程序才能启动。 我想,您必须保证不存在具有相似名称的第 3 方进程。 (为此,为您的程序提供一个更长且唯一的名称)。并且您的程序名称不得包含模式“grep”。

结合正常的程序启动(由 cron 守护进程在 cron 表中配置)可以很好地工作。 如果您的检查是作为外部脚本编写的,则 crontab 中的条目可能看起来像

 <time_specification>  <your_starter_script>  <your_program> ...

2重要说明:your_starter_script的退出代码必须为0,以防您的程序无法启动,最好完全禁止该脚本写入stdout或stderr。

这样的入门非常简短,是一个简单的编程练习。因此我觉得没有必要提供它的完整代码。

【讨论】:

    【解决方案3】:

    或者,也许比我之前的答案(使用at 安排脚本在 5 分钟内再次运行)更简单,就是通过使用非终止循环使您的脚本成为守护进程,如下所示:

    while(1) {
      // whatever your script does here....
      sleep(300) //wait 5 minutes
    }
    

    然后,您可以通过cronat 完全取消调度。只需从命令行在后台运行您的脚本,如下所示:

    /path/to/your/script &
    

    或者,在 /etc/rc.local 中添加/path/to/your/script 以使您的脚本在机器启动时自动启动。

    【讨论】:

      【解决方案4】:

      与其使用cron 每 5 分钟运行一次脚本,不如使用at 安排脚本在完成 5 分钟后再次运行。在您的脚本接近尾声时,您可以使用 shell_exec() 运行 at 命令来安排您的脚本在 5 分钟后再次运行,如下所示:

      at now + 5 minutes /path/to/script
      

      【讨论】:

      • 这种使用“at”的解决方案并不可靠。如果曾经出现任何问题,例如进程已被杀死或 UNIX Linux 调度机制出现临时问题(过载),那么“轮回”链将被破坏,因此您需要组织一个“保姆”,即不仅仅是简单的监控,它会监视并重新启动链如果它坏了。
      • 效率不高,安排进程是cron的任务(你不必重新发明轮子)。
      【解决方案5】:

      我使用bash script 的解决方案是这样的:

      exec 9>/path/to/lock/file
      if ! flock -n 9  ; then
          echo "another instance is running";
          exit 1
      fi
      # this now runs under the lock until 9 is closed (it will be closed automatically when the script ends)  
      

      /var/lock/file 中创建了一个文件描述符9&gt;,并且flock 将退出一个正在尝试运行的新进程,除非没有其他脚本实例正在运行。

      How can I ensure that only one instance of a script is running at a time (mutual exclusion)?

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-24
      • 1970-01-01
      • 2021-05-02
      • 2019-06-19
      • 2015-07-18
      • 2013-03-22
      • 1970-01-01
      相关资源
      最近更新 更多