【问题标题】:Automatically Restart PHP Script on Exit退出时自动重启 PHP 脚本
【发布时间】:2012-04-05 14:34:02
【问题描述】:

有没有一种方法可以在 PHP 脚本退出时自动重新启动它,无论它是否已正确退出、是否因错误或内存使用最大化等而终止?

【问题讨论】:

  • 在 shell 脚本中将其放入无限循环中?
  • 它可能会工作,但我必须让一个脚本的 8 个线程同时运行,并且如果它们退出则全部重新启动。那么我需要运行 8 个 shell 脚本,对吗?好像效率不高。
  • PHP脚本只有被调用才能执行。所以只使用php没有我想的办法。但是后台进程可以做到这一点。如果您可以编写这样一个可以检查正在运行的进程并执行所需步骤的后台进程,那么您可以做到。
  • 我发现supervisord 对此非常有用。你可以在纯PHP中实现这样的进程监督/控制/重启/守护进程行为,但是为什么不使用已经存在的东西呢?
  • php 守护进程从来都不是很可靠。但我已经使用 cron 作业每 5 分钟检查一次,并在必要时重新启动。如果您可以忍受 1 分钟的停机时间,请尝试 cron。

标签: php bash cron sh restart


【解决方案1】:

这样做:

sleep(1);
system('php '.__FILE__);

【讨论】:

    【解决方案2】:

    为了补充ow3n 的答案,PHP CLI 脚本与任何其他命令一样,可以在无限while 循环中轻松重复。它的工作方式与单独运行命令完全相同,但在结束时会再次运行。当您想终止循环时,只需按 Ctrl+C

    编辑:如果您的进程catches signals(如果它是12 factor app,则应该这样做),您需要按住Ctrl +C 而不是仅仅按下它。进程会捕捉到按下(应该会自行终止),但while 循环会再次重新启动进程(如果您只想重新启动进程而不是终止它,这很有用)。如果您的进程没有捕获信号,则 Ctrl+C 将杀死进程和 while 循环。

    这是最简单的方法,无需任何额外输出:

    while :; do [command]; done

    while :; do echo "test" && sleep 1; done

    while :; do php myscript.php; done

    也就是说,我有点担心你的 cmets 中的含义:

    关于线程:

    1. 我会三思而后行在 PHP 中使用线程。使用该模型的语言具有比 PHP 更好/稳定/成熟的环境(例如 Java)。
    2. 使用多进程而不是多线程可能看起来效率低下,但相信我,它在简单性、可维护性、易于调试等方面弥补了它的不足。多线程可以很快将您带到maintenance hell,它解释了Actor Model的出现。
    3. 效率是业务要求吗?这通常是由于资源有限(如嵌入式设备),但我怀疑这是你的情况,否则你不会使用 PHP。考虑一下,如今,people are much more expensive than computers。因此,为了最大限度地降低成本,最大化您的人员而不是您的计算机的效率会更有益 (see the Unix philosophy's Rule of Economy)。多线程如果做得好,可以通过优化计算机的使用来为您节省一些钱,但会为您的员工带来更多麻烦。
    4. 如果您绝对必须使用线程,我强烈建议您考虑 Actor 模型。 Akka 是一个很棒的实现。

    关于流程:

    1. 如果您在开发中运行脚本,[每种类型] 一个进程就足够了。
    2. 如果您在生产环境中运行脚本,则应依靠流程管理器来管理您的流程,例如 Upstart
    3. 避免将您的流程变成守护进程。
    4. 了解12 factor applications,尤其是processesconcurrencydisposability

    更新:Containers 让运行和管理一系列进程变得更加容易。

    【讨论】:

      【解决方案3】:

      只有我的 5 美分。 PCNTL 使用自定义错误处理重新启动..

      register_shutdown_function("shutdownHandler");
      
      function shutdownHandler() //will be called when php script ends.
      {
          $lasterror = error_get_last();
          switch ($lasterror['type'])
          {
              case E_ERROR:
              case E_CORE_ERROR:
              case E_COMPILE_ERROR:
              case E_USER_ERROR:
              case E_RECOVERABLE_ERROR:
              case E_CORE_WARNING:
              case E_COMPILE_WARNING:
              case E_PARSE:
                  $error = "[SHUTDOWN] lvl:" . $lasterror['type'] . " | msg:" . $lasterror['message'] . " | file:" . $lasterror['file'] . " | ln:" . $lasterror['line'];
                  myCustomLogHandler("FATAL EXIT $error\n");
          }
          $_ = $_SERVER['_'];
          global $_; 
          pcntl_exec($_);
      }
      

      【讨论】:

      • 重启几次后我得到Notice: Undefined index: _ in
      【解决方案4】:

      要重新启动脚本每次它停止运行,您可以将user1179181 的脚本包装在一个循环中。

      #!/bin/bash
      
      while true
      do
          if ps ax | grep -v grep | grep "script.php"
          then
              echo "process is running..."
          else
              clear
              date
              echo "process not running, relaunching..."
              php script.php
          fi
          sleep 10
      done
      

      【讨论】:

        【解决方案5】:

        使用 CRON

        #!/bin/sh
        process = 'script.php'
        
        if ps ax | grep -v grep | grep $process
        then
            echo "$process is running..."
        else
            echo "$process not running, relaunching..."
            php /your/php/script.php
        fi
        

        【讨论】:

          【解决方案6】:

          这里有龙。

          高级用法,不适合胆小的人。

          要启动单独的进程,请将 restartMyself 函数更改为:

          <?php
              $restartMyself = function () {
                  global $_, $argv; 
                  if(!pcntl_fork()) // We only care about the child fork
                      pcntl_exec($_, $argv);
                  die; // insist that we don't tolerate errors  
              }
          

          这当然是一种冒险,如果你设法在进程中捕获它们,那么追逐重新启动的进程对你的杀戮免疫。 =)

          确保脚本有足够的时间执行此操作,以便您有足够的时间来杀死它。

          试试:

          $ kill -9
          

          或:

          $ kill -s KILL
          

          坚持不死生物保持死亡。这不是闲置的参考,因为您最好考虑僵尸和孤儿,但最终即使是最艰难的进程通常仍然需要父进程,因此终止 shell 会话应该让它们休息。

          所有标准免责声明均适用,祝你好运! =)

          【讨论】:

            【解决方案7】:

            PHP 脚本也可以使用 PCNTL 重新启动。

            免责声明:本练习的目的只是为了证明 PHP 完全有能力重新启动自己并回答问题:

            有没有一种方法可以在 PHP 脚本退出时自动重新启动,无论它是否已正确退出,还是因错误而终止?

            因此,我们无法详细介绍有关 unix 进程的任何细节,因此我建议您从PCNTL book 开始,或参考 了解更多详细信息。

            在示例中,我们假设它是在 *nix 环境中从带有半体面 shell 的终端使用以下命令启动的 PHP CLI 脚本:

            $ php i-can-restart-myself.php 0
            

            我们传递一个重启计数属性作为进程重启的指示器。

            我可以自动重启 PHP 脚本吗?

            是的,我可以!

            <?php
                echo ++$argv[1];     // count every restart
                $_ = $_SERVER['_'];  // or full path to php binary
            
                echo "\n======== start =========\n";
                // do a lot of stuff
                $cnt = 0;
                while( $cnt++ < 10000000 ){}
                echo "\n========== end =========\n";
            
                // restart myself
                pcntl_exec($_, $argv);
            

            不管是否已经正常终止?

            是的,如果终止,我可以重新启动!

            <?php
                echo ++$argv[1];    
                $_ = $_SERVER['_']; 
            
                register_shutdown_function(function () {
                    global $_, $argv; // note we need to reference globals inside a function
                    // restart myself
                    pcntl_exec($_, $argv);
                });
            
                echo "\n======== start =========\n";
                // do a lot of stuff
                $cnt = 0;
                while( $cnt++ < 10000000 ){}
                echo "\n========== end =========\n";
            
                die; // exited properly
                // we can't reach here 
                pcntl_exec($_, $argv);
            

            还是因错误而终止?

            同上!

            <?php
                echo ++$argv[1];    
                $_ = $_SERVER['_']; 
            
                register_shutdown_function(function () {
                    global $_, $argv; 
                    // restart myself
                    pcntl_exec($_, $argv);
                });
            
                echo "\n======== start =========\n";
                // do a lot of stuff
                $cnt = 0;
                while( $cnt++ < 10000000 ){}
                echo "\n===== what if? =========\n";
                require 'OOPS! I dont exist.'; // FATAL Error:
            
                // we can't reach here
                echo "\n========== end =========\n";        
                die; // exited properly
                // we can't reach here 
                pcntl_exec($_, $argv);
            

            但你知道我想要的不止这些,对吧?

            当然!我可以在 kill 时重新启动,甚至 Ctrl-C 集线器!

            <?php
                echo ++$argv[1];     
                $_ = $_SERVER['_'];  
                $restartMyself = function () {
                    global $_, $argv; 
                    pcntl_exec($_, $argv);
                };
                register_shutdown_function($restartMyself);
                pcntl_signal(SIGTERM, $restartMyself); // kill
                pcntl_signal(SIGHUP,  $restartMyself); // kill -s HUP or kill -1
                pcntl_signal(SIGINT,  $restartMyself); // Ctrl-C
            
                echo "\n======== start =========\n";
                // do a lot of stuff
                $cnt = 0;
                while( $cnt++ < 10000000 ){}
                echo "\n===== what if? =========\n";
                require 'OOPS! I dont exist.'; // FATAL Error:
            
                // we can't reach here
                echo "\n========== end =========\n";        
                die; // exited properly
                // we can't reach here 
                pcntl_exec($_, $argv);
            

            我现在如何终止它?

            如果您通过按住 Ctrl-C 来淹没进程,您可能会在关机的某个地方捕获它。

            我不想看到所有这些错误,我也可以重新开始吗?

            没问题我也能处理错误!

            <?php
                echo ++$argv[1];     
                $_ = $_SERVER['_'];  
                $restartMyself = function () {
                    global $_, $argv; 
                    pcntl_exec($_, $argv);
                };
                register_shutdown_function($restartMyself);
                pcntl_signal(SIGTERM, $restartMyself);   
                pcntl_signal(SIGHUP,  $restartMyself);   
                pcntl_signal(SIGINT,  $restartMyself);   
                set_error_handler($restartMyself , E_ALL); // Catch all errors
            
                echo "\n======== start =========\n";
                // do a lot of stuff
                $cnt = 0;
                while( $cnt++ < 10000000 ){}
            
                echo $CAREFUL_NOW; // NOTICE: will also be caught
            
                // we would normally still go here
                echo "\n===== what if? =========\n";
                require 'OOPS! I dont exist.'; // FATAL Error:
            
                // we can't reach here
                echo "\n========== end =========\n";        
                die; // exited properly
                // we can't reach here 
                pcntl_exec($_, $argv);
            

            虽然乍一看这似乎工作正常,因为 pcntl_exec 在同一个进程中运行,我们并没有注意到事情非常错误。如果你想生成一个新进程并让旧进程死掉,这是一个完全可行的替代方案,请参阅下一篇文章,你会注意到每次迭代都会启动 2 个进程,因为我们触发了一个 PHP NOTICE 和一个错误由于共同的疏忽.这当然可以通过在错误处理程序中调用 pcntl_exec 之后确保我们 die() 或 exit() 来轻松纠正,否则 PHP 假定容忍度被接受并继续。

            我要说明的一点是,即使您能够重新启动失败的脚本,这也不是允许代码损坏的借口。尽管这种做法可能存在可行的用例,但我强烈反对将失败时重新启动作为脚本因错误而失败的解决方案! 正如我们从这些示例无法确切知道它失败的位置,因此我们无法确定它将从哪里重新开始。寻求看似工作正常的“快速修复”解决方案现在可能比以前遇到更多问题。

            我更愿意通过一些适当的单元测试来解决缺陷,这将有助于排除罪魁祸首,以便我们可以纠正问题。如果 PHP 内存不足,可以通过在使用后取消设置变量来保守地使用资源来避免。 (顺便说一句。你会发现分配 null 比使用 unset 获得相同的结果要快得多)但我担心,如果不解决,重新启动肯定会成为你已经拥有的蠕虫罐头的合适开启者。

            【讨论】:

            • 这确实对我有用,但是我必须直接在第二个参数中传递文件路径,或者进程什么也不做(就像在没有文件的情况下调用 php)。我继续前进并直接通过了 PHP 路径,以进行良好的衡量:pcntl_exec('/usr/local/bin/php', [ __FILE__ ]);
            【解决方案8】:

            假设:

            • 调用同一个脚本文件 x 次
            • 如果中断/结束/停止,请重新调用
            • 无限循环的自动化过程

            想到一些选项来实现您的目标 [基于 Unix/Linux 的解决方案]:

            1. 使用BASH script,以便停止/结束/中断后的PHP脚本可以恢复:

              #!/bin/bash
              clear
              date
              
              php -f my_php_file.php
              sleep 100
              # rerun myself
              exec $0
              
            2. The Fat Controller 执行处理程序,这是您正在寻找的关键功能:

              • 用于重复运行其他程序
              • 可以并行运行任何脚本/程序的多个实例
              • 任何脚本在执行完毕后都可以重新运行

              与 CRON 非常相似,但这些关键特性正是您所需要的

            3. 使用CRON,你可以让它每隔例如一分钟调用你的PHP脚本,然后在你的PHP脚本开始时使用这段代码,你将能够控制运行的线程数,如果已经有 8 个则退出:

              exec('ps -A | grep ' . escapeshellarg(basename(__FILE__)) , $results);
              if (count($results) > 7) {
                echo "8 Already Running\n"
                die(0);
              }
              

              搜索与当前文件相同路径运行的进程,并返回找到的进程数。然后,如果它们大于 7,则退出。

            【讨论】:

            • 为您基于目标 (unix/linux) 的解决方案使用经过实际测试的解决方案更新了答案。
            猜你喜欢
            • 2013-03-20
            • 2011-09-16
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-02-21
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多