【问题标题】:Run cron job only if it isn't already running仅在尚未运行时运行 cron 作业
【发布时间】:2011-01-22 22:15:42
【问题描述】:

我正在尝试将 cron 作业设置为我创建的守护程序的一种看门狗。如果守护程序出错并失败,我希望 cron 作业定期重新启动它......我不确定这有多大可能,但我通读了几个 cron 教程并且找不到任何可以做的事情正在寻找...

我的守护程序是从一个 shell 脚本启动的,所以我真的只是在寻找一种方法来运行一个 cron 作业,前提是该作业的上一次运行尚未运行。

I found this post,它确实为我尝试使用锁定文件所做的事情提供了解决方案,但我不确定是否有更好的方法来做到这一点......

【问题讨论】:

    标签: linux bash cron watchdog


    【解决方案1】:

    使用flock。这是新的。它更好。

    现在您不必自己编写代码。在此处查看更多原因:https://serverfault.com/a/82863

    /usr/bin/flock -n /tmp/my.lockfile /usr/local/bin/my_script
    

    【讨论】:

    • 一个非常简单的解决方案
    • 最好的解决方案,我已经用了很长时间了。
    • 我还在这里创建了一个不错的 cron 模板要点:gist.github.com/jesslilly/315132a59f749c11b7c6
    • setlocks6-setlockchpstrunlock 在它们的非阻塞模式下是替代方案,不仅在 Linux 上可用。 unix.stackexchange.com/a/475580/5132
    • 我觉得这应该是公认的答案。如此简单!
    【解决方案2】:

    我为自己编写的打印后台处理程序执行此操作,它只是一个 shell 脚本:

    #!/bin/sh
    if ps -ef | grep -v grep | grep doctype.php ; then
            exit 0
    else
            /home/user/bin/doctype.php >> /home/user/bin/spooler.log &
            #mailing program
            /home/user/bin/simplemail.php "Print spooler was not running...  Restarted." 
            exit 0
    fi
    

    它每两分钟运行一次,非常有效。如果由于某种原因进程没有运行,我会通过电子邮件向我发送特殊信息。

    【讨论】:

    • 不是一个非常安全的解决方案,但是,如果有其他进程与您在 grep 中所做的搜索相匹配怎么办? rsanden 的回答使用 pidfile 防止了这种问题。
    • 这个轮子已经在别处发明了 :) 例如,serverfault.com/a/82863/108394
    • 你可以用grep [d]octype.php代替grep -v grep | grep doctype.php
    • 请注意,如果是cron运行脚本,则不需要&
    【解决方案3】:

    正如其他人所说,编写和检查 PID 文件是一个很好的解决方案。这是我的 bash 实现:

    #!/bin/bash
    
    mkdir -p "$HOME/tmp"
    PIDFILE="$HOME/tmp/myprogram.pid"
    
    if [ -e "${PIDFILE}" ] && (ps -u $(whoami) -opid= |
                               grep -P "^\s*$(cat ${PIDFILE})$" &> /dev/null); then
      echo "Already running."
      exit 99
    fi
    
    /path/to/myprogram > $HOME/tmp/myprogram.log &
    
    echo $! > "${PIDFILE}"
    chmod 644 "${PIDFILE}"
    

    【讨论】:

    • +1 使用 pidfile 可能比使用 grep 搜索具有相同名称的正在运行的程序更安全。
    • /path/to/myprogram & > $HOME/tmp/myprogram.log & ??????你的意思是 /path/to/myprogram >> $HOME/tmp/myprogram.log &
    • 脚本完成后文件不应该被删除吗?还是我遗漏了一些非常明显的东西?
    • @matteo:是的,你是对的。几年前我在笔记中修复了这个问题,但忘了在这里更新。更糟糕的是,我在您的评论中也错过了它,只注意到“>”与“>>”。对此感到抱歉。
    • @Hamzahfrq:它是这样工作的:脚本首先检查PID文件是否存在(“[ -e "${PIDFILE}" ]”。如果不存在,那么它将在后台启动程序,写它的 PID 到一个文件(“echo $! > "${PIDFILE}"”),然后退出。如果 PID 文件确实存在,那么脚本将检查您自己的进程(“ps -u $(whoami) -opid=”)并查看您是否正在运行一个相同的进程PID("grep -P "^\s*$(cat ${PIDFILE})$"")。如果你不是,那么它会像以前一样启动程序,用新的 PID 覆盖 PID 文件,然后退出。我认为没有理由修改脚本;是吗?跨度>
    【解决方案4】:

    令人惊讶的是,没有人提到run-one。我已经解决了我的问题。

     apt-get install run-one
    

    然后在您的 crontab 脚本之前添加 run-one

    */20 * * * * * run-one python /script/to/run/awesome.py
    

    查看this askubuntu SE 的答案。您也可以在那里找到详细信息的链接。

    【讨论】:

    • 值得一提的是,这个工具在 Ubuntu 20 中开箱即用(也可能是之前的版本)
    • 帮助很大
    【解决方案5】:

    不要试图通过 cron 来做。无论如何都让 cron 运行一个脚本,然后让脚本决定程序是否正在运行并在必要时启动它(注意你可以使用 Ruby 或 Python 或你最喜欢的脚本语言来执行此操作)

    【讨论】:

    • 经典的方法是读取服务启动时创建的PID文件,检查该PID的进程是否还在运行,如果没有则重启。
    【解决方案6】:

    您也可以直接在您的 crontab 中以单行方式执行此操作:

    * * * * * [ `ps -ef|grep -v grep|grep <command>` -eq 0 ] && <command>
    

    【讨论】:

    • 不是很安全,万一还有其他的命令匹配搜索grep呢?
    • 这也可以写成 * * * * * [ ps -ef|grep [c]ommand -eq 0 ] && 其中将命令的第一个字母括在括号中会将其从 grep 结果中排除。跨度>
    • 我必须使用以下语法:[ "$(ps -ef|grep [c]ommand|wc -l)" -eq 0 ] &amp;&amp; &lt;command&gt;
    • 这太可怕了。 [ $(grep something | wc -l) -eq 0 ] 是写! grep -q something 的一种非常迂回的方式。所以你只需要ps -ef | grep '[c]ommand' || command
    • (另外,顺便说一句,如果你真的想计算匹配行的数量,那就是grep -c。)
    【解决方案7】:

    我在运行php脚本时的做法是:

    crontab:

    * * * * * php /path/to/php/script.php &
    

    php代码:

    <?php
    if (shell_exec('ps aux | grep ' . __FILE__ . ' | wc  -l') > 1) {
        exit('already running...');
    }
    // do stuff
    

    此命令正在系统进程列表中搜索当前的php文件名 如果存在,则行计数器 (wc -l) 将大于 1,因为搜索命令本身包含文件名

    所以如果你运行 php crons 将上面的代码添加到你的 php 代码的开头,它只会运行一次。

    【讨论】:

    • 这是我所需要的,因为所有其他解决方案都需要在客户端服务器上安装一些东西,而我无权这样做。
    【解决方案8】:

    作为 Earlz 回答的后续行动,您需要一个包装脚本,该脚本在启动时创建一个 $PID.running 文件,并在结束时删除。包装脚本调用您希望运行的脚本。包装器是必要的,以防目标脚本失败或出错,pid 文件被删除..

    【讨论】:

    • 哦,太棒了...我从没想过使用包装器...我想不出使用锁定文件的方法,因为我不能保证文件会被删除,如果守护程序出错了...包装器可以完美运行,我将尝试 jjclarkson 的解决方案,但如果不起作用,我会这样做...
    【解决方案9】:

    使用lockrun,您无需为您的 cron 作业编写包装脚本。 http://www.unixwiz.net/tools/lockrun.html

    【讨论】:

      【解决方案10】:

      我建议使用现有的工具,例如monit,它会监控和自动重启进程。有更多信息可用here。它应该在大多数发行版中都很容易获得。

      【讨论】:

      • 除此之外的每个答案都回答了表面问题“我的 cron 作业如何确保它只运行一个实例?”当真正的问题是“我怎样才能让我的进程在重新启动时保持运行?”,而正确的答案确实是不使用 cron,而是使用像 monit 这样的进程主管。其他选项包括runits6,或者,如果您的发行版已经使用 systemd,则只需为需要保持活动状态的进程创建一个 systemd 服务。
      【解决方案11】:
      # one instance only (works unless your cmd has 'grep' in it)
      ALREADY_RUNNING_EXIT_STATUS=0
      bn=`basename $0`
      proc=`ps -ef | grep -v grep | grep "$bn" | grep -v " $$ "`
      [ $? -eq 0 ] && {
          pid=`echo $proc | awk '{print $2}'`
          echo "$bn already running with pid $pid"
          exit $ALREADY_RUNNING_EXIT_STATUS
      }
      

      更新 .. 使用羊群的更好方法:

      /usr/bin/flock -n /tmp/your-app.lock /path/your-app args 
      

      【讨论】:

        【解决方案12】:

        This one 从来没有让我失望过:

        one.sh

        LFILE=/tmp/one-`echo "$@" | md5sum | cut -d\  -f1`.pid
        if [ -e ${LFILE} ] && kill -0 `cat ${LFILE}`; then
           exit
        fi
        
        trap "rm -f ${LFILE}; exit" INT TERM EXIT
        echo $$ > ${LFILE}
        
        $@
        
        rm -f ${LFILE}
        

        cron 作业

        * * * * * /path/to/one.sh <command>
        

        【讨论】:

          【解决方案13】:

          我建议以下作为对rsanden's answer 的改进(我会作为评论发布,但没有足够的声誉......):

          #!/usr/bin/env bash
          
          PIDFILE="$HOME/tmp/myprogram.pid"
          
          if [ -e "${PIDFILE}" ] && (ps -p $(cat ${PIDFILE}) > /dev/null); then
            echo "Already running."
            exit 99
          fi
          
          /path/to/myprogram
          

          这避免了可能的错误匹配(以及 grepping 的开销),并且它抑制了输出并且仅依赖于 ps 的退出状态。

          【讨论】:

          • 您的 ps 命令将匹配系统上其他用户的 PID,而不仅仅是您自己的。在ps 命令中添加“-u”会改变退出状态的工作方式。
          【解决方案14】:

          简单的自定义php就足够实现了。无需与 shell 脚本混淆。

          假设你想运行 php /home/mypath/example.php 如果没有运行

          然后使用下面的自定义 php 脚本来做同样的工作。

          创建以下/home/mypath/forever.php

          <?php
              $cmd = $argv[1];
              $grep = "ps -ef | grep '".$cmd."'";
              exec($grep,$out);
              if(count($out)<5){
                  $cmd .= ' > /dev/null 2>/dev/null &';
                  exec($cmd,$out);
                  print_r($out);
              }
          ?>
          

          然后在你的 cron 中添加以下内容

          * * * * * php /home/mypath/forever.php 'php /home/mypath/example.php'
          

          【讨论】:

            【解决方案15】:

            文档:https://www.timkay.com/solo/

            solo 是一个非常简单的脚本(10 行),它可以防止程序 一次运行多个副本。使用 cron 来制作很有用 确保在前一个作业完成之前不会运行作业。

            例子

            * * * * * solo -port=3801 ./job.pl blah blah
            

            【讨论】:

              【解决方案16】:

              如果您打算走这条路,请考虑使用 pgrep(如果可用)而不是 ps 通过 grep 管道。不过,就个人而言,我已经从表单脚本中获得了很多里程

              while(1){
                call script_that_must_run
                sleep 5
              }
              

              虽然这可能会失败,但 cron 作业 通常是处理重要内容的最佳方式。只是另一种选择。

              【讨论】:

              • 这只会一遍又一遍地启动守护进程,并不能解决上述问题。
              猜你喜欢
              • 2021-09-24
              • 2014-11-02
              • 1970-01-01
              • 2020-03-16
              • 2014-12-05
              • 2015-05-15
              • 2014-04-20
              相关资源
              最近更新 更多