无论 PHP 脚本是正常退出、因错误退出或达到最大内存使用量而终止,是否有一种自动重新启动 PHP 脚本的方法?
无论 PHP 脚本是正常退出、因错误退出或达到最大内存使用量而终止,是否有一种自动重新启动 PHP 脚本的方法?
免责声明:此次演示的目的只是为了证明PHP完全有能力重新启动自身,并回答以下问题:
是否有一种方式可以在PHP脚本退出时自动重新启动它,无论其是否已正确退出或因错误而终止?
因此,我们的范围不会涉及Unix进程的任何细节,建议您从PCNTL书籍开始学习,或者参考php-pcntl获取更多详细信息。需要开发人员谨慎决定,仅仅因为我们可以这样做,并不代表这是明智之举。除去所有认真的事情,让我们来玩一些乐趣。
在示例中,我们将假设它是一个PHP CLI脚本,在一个半好的shell中从终端启动,使用以下命令:
$ php i-can-restart-myself.php 0
<?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);
<?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);
// NOTE: consider fork and exiting here as error handler
};
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和ERROR,这是由于常见的疏忽而引起的。这可以通过确保在调用pcntl_exec
后使用die()
或exit()
来纠正,因为实施错误处理程序后PHP假定容忍度已被接受并继续运行。缩小错误报告级别而不是捕获E_ALL
也将更加负责。$_SERVER['_']
是做什么用的? - Marco Marsala$_
,它指向最后一个可执行文件,在我们的情况下,它是PHP解释器二进制文件的完整路径,例如/usr/bin/php
,而$argv
包含传递给$_
的所有参数,因此pcntl_exec($_, $argv)
实际上等同于在命令行上键入php i-can-restart-myself.php 1
。 - nickl-要启动一个单独的进程,请将restartMyself
函数更改为包括pcntl_fork()
调用,根据手册,它会生成具有唯一PID
的新进程,我们仅在子进程中重新启动脚本。然后,在处理第一个错误(PHP警告)时,我们清除当前进程,释放资源并防止执行继续,同时触发ERROR并启动另一个进程。
一个基本的多进程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 [PID]
或者:
$ kill -s KILL [PID]
<?php
echo ++$argv[1]; // count every restart with argument 1
$_ = $_SERVER['_']; // full path to PHP interpreter binary
$restartMyself = function () {
global $_, $argv, $pid; // track PID in global scope
if(!$pid ??= pcntl_fork()) // only fork if $pid is null
pcntl_exec($_, $argv); // only restart as the child fork
die; // exit parent process when called in shutdown function
};
register_shutdown_function($restartMyself);// catch on exit
pcntl_signal(SIGTERM, $restartMyself); // catch kill
pcntl_signal(SIGHUP, $restartMyself); // catch kill -s HUP or kill -1
pcntl_signal(SIGINT, $restartMyself); // catch Ctrl-C
set_error_handler($restartMyself , E_ALL); // catch all errors
// do a lot of stuff
for ($cnt = 0; $cnt++ < 999999999;){}
echo $UNDEFINED_VAR; // WARNING: will be caught,
// script restarted as new process,
// and current process terminated
// we will not reach here anymore
$ php i-can-respawn-myself.php 0
nJoy!
假设:
有一些选项可以考虑来实现你的目标[基于Unix/Linux的解决方案]:
Use a BASH script, so that the PHP script can be resumed after it stopped/ended/interrupted:
#!/bin/bash
clear
date
php -f my_php_file.php
sleep 100
# rerun myself
exec $0
The Fat Controller execution handler, these are the key features you're looking for:
与CRON非常相似,但其关键特性正是您所需的
Using CRON, you can have it call your PHP script at intervals of e.g. every minute, then with this code at the very beginning of you PHP script, you will be able to control the number of processes running, exiting if there are already 8:
exec('ps -A | grep ' . escapeshellarg(basename(__FILE__)) , $results);
if (count($results) > 7) {
echo "8 Already Running\n"
die(0);
}
while
循环中轻松重复。它将与单独运行该命令完全相同,但在结束时会再次运行。只需在想要终止循环时按下 Ctrl+C。
编辑:如果您的进程捕获信号(如果它是12因素应用程序,则应该),则需要按住Ctrl+C而不仅仅是按下它。按键会被进程捕获(进程应该自行终止),但while
循环会重新启动进程(如果您只想重新启动进程而不是终止它,则这是有用的)。如果您的进程没有捕获信号,那么Ctrl+C将同时杀死进程和while
循环。
以下是最简单的方法,没有额外的输出:
while :; do [command]; done
while :; do echo "test" && sleep 1; done
while :; do php myscript.php; done
话虽如此,我有点担心您在评论中的含义:
关于线程:
关于进程:
更新:容器使运行和管理一组进程变得更加容易。
#!/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
我个人的意见是:使用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($_);
}
set_error_handler
注册错误处理程序部分。 - nickl-如果想要在脚本停止运行后每次都重新启动它,可以将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
就这样做:
sleep(1);
system('php '.__FILE__);