PHP后台进程

41

我正在尝试制作一个PHP脚本,脚本已完成,但完成其设计的过程需要大约10分钟。这不是问题,但我认为我必须在此期间保持页面加载状态,这很烦人。我能否让它这样做:我开始处理过程,然后在10分钟后回来,只查看它生成的日志文件?

10个回答

50

嗯,你可以使用 "ignore_user_abort(true)"

这样脚本将会继续运行(注意脚本的执行时间,或许可以添加 "set_time_limit(0)")

但是要注意,使用这两行代码将无法停止脚本的执行:

ignore_user_abort(true); 
set_time_limit(0);

除非你能直接访问服务器并在那里终止进程!(我曾经遇到过这种情况,进入了一个无尽循环,一遍又一遍地调用自身,导致服务器停滞不前,还被人大声呵斥……)

这太棒了。这也非常适用于WordPress的ajax调用'wp_ajax_nopriv_your_trigger'。在你的钩子中使用上述两种方法,并调用(可能使用调度程序)http://yourdomain.com/wp-admin/admin-ajax.php?action=your_trigger。 - Ardee Aram
像魔法一样好用!我正在将一个文件上传到我的服务器,然后使用这种方法的脚本将其上传到S3,以便我可以轻松地向用户显示上传进度。我在我的站点中刷新、发送其他请求等等。结果文件就出现在S3中了,而我的日志也显示了上传的进程。:D - Michael J. Calkins
@azure_ardee WordPress已经有一个模拟cron系统来执行这样的任务。请参见http://codex.wordpress.org/Function_Reference/wp_cron。 - Bob Gregor

27

听起来你需要一个队列和一个用于处理队列的外部脚本。

例如,你的PHP脚本应该将一个条目放入数据库表中并立即返回。然后,每分钟运行的cron检查队列并为每个作业分叉一个进程。

这里的优点是不会锁定Apache线程10分钟。


8
我在Windows下遇到了很多类似的问题。我的情况与众不同,我并不关心“脚本”的响应 - 我希望脚本在忙碌工作时能够开始并允许其他页面请求通过。
由于某种原因,我遇到了一些问题,要么挂起其他请求,要么在大约60秒后超时(apache和php都设置为在大约20分钟后超时)。另外,火狐浏览器默认情况下在5分钟后超时,所以在那个时间点之后,如果不更改火狐浏览器的设置,你就无法知道正在发生什么。
最终,我使用进程打开和进程关闭方法,以cli模式打开一个php文件,如下所示: pclose(popen("start php myscript.php", "r"));
这将(使用start)打开php进程,然后杀死启动进程,使php运行所需的时间任意长 - 再次需要手动杀死进程。它不需要设置任何超时,并且您可以让调用它的当前页面继续输出更多详细信息。
唯一的问题是,如果您需要向脚本发送任何数据,则必须通过另一个源或通过“命令行”传递参数来完成,这样并不安全。
对我们需要的内容来说效果很好,并确保脚本始终启动并允许无干扰运行。

谢谢,这正是我需要的。这里还有一些其他的解决方法:http://www.somacon.com/p395.php - Bryan Waters

5

4

还有另一种选择,您可以使用运行脚本CLI...它将在后台运行,如果需要,甚至可以将其作为cronjob运行。

例如:

> #!/usr/bin/php -q

<?php

//process logs

?>

这可以设置为cronjob,并且没有时间限制地执行...虽然此示例仅适用于基于Unix的操作系统。

顺便提一下,我有一个运行无限循环的php脚本,它执行一些处理操作并已经连续运行了过去三个月。


我认为你很幸运。根据我的研究和经验,让一个PHP脚本连续运行超过几天而不停止是很困难的——它并没有被设计成这样,至少我听说是这样的。但是几个小时应该没问题。这一行代码可能会对某些人有所帮助:ini_set('max_execution_time',28800); // 8 小时 - PJ Brunet

3

您可以使用ignore_user_abort()函数 - 这样脚本将会继续运行,即使您关闭浏览器或跳转到其他页面。


2

想一想 Gearman

Gearman是一个通用的应用程序框架,可将工作分配给多台计算机或进程。它允许应用程序并行完成任务,平衡处理负载,并在不同语言之间调用函数。该框架可用于各种应用程序,从高可用性的网站到数据库复制事件的传输。

此扩展提供编写Gearman客户端和工作者的类。 - 来源php手册

Gearman官方网站


2
除了bastiandoeen的答案外,您还可以将ignore_user_abort(true);cUrl请求结合使用。

伪造一个请求中止设置一个低的CURLOPT_TIMEOUT_MS并在连接关闭后继续处理:

function async_curl($background_process=''){

    //-------------get curl contents----------------

    $ch = curl_init($background_process);
    curl_setopt_array($ch, array(
        CURLOPT_HEADER => 0,
        CURLOPT_RETURNTRANSFER =>true,
        CURLOPT_NOSIGNAL => 1, //to timeout immediately if the value is < 1000 ms
        CURLOPT_TIMEOUT_MS => 50, //The maximum number of mseconds to allow cURL functions to execute
        CURLOPT_VERBOSE => 1,
        CURLOPT_HEADER => 1
    ));
    $out = curl_exec($ch);

    //-------------parse curl contents----------------

    //$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    //$header = substr($out, 0, $header_size);
    //$body = substr($out, $header_size);

    curl_close($ch);

    return true;
}

async_curl('http://example.com/background_process_1.php');

NB

如果您希望cURL在不到1秒的时间内超时,可以使用CURLOPT_TIMEOUT_MS。但是,在“类Unix系统”上,libcurl会立即超时并显示错误“cURL Error(28):Timeout was reached”,如果设置值小于1000ms,这是一个已知的bug/"feature"。此行为的解释如下:

[...]

解决方法是使用CURLOPT_NOSIGNAL禁用信号。

优点

  • 无需切换方法(兼容Windows和Linux)
  • 无需通过标头和缓冲区实现连接处理(独立于浏览器和PHP版本)

缺点

  • 需要curl扩展

资源


1

Zuk.

我相信这会起作用:

<?php 

pclose(popen('php /path/to/file/server.php &'));
echo "Server started. [OK]"; 

?>

"&"很重要,它告诉shell不要等待进程退出。 此外,您可以在php中使用此代码(如“bastiandoeen”所说)。
ignore_user_abort(true); 
set_time_limit(0);

在你的服务器停止命令中:
<?php

$output;
exec('ps aux | grep -ie /path/to/file/server.php | awk \'{print $2}\' | xargs kill -9 ', $output);
    echo "Server stopped. [OK]";

?>

-1
只需在任何输出之前调用StartBuffer(),并在希望客户端关闭连接时调用EndBuffer()。在调用EndBuffer()之后的代码将在没有客户端连接的情况下在服务器上执行。
私有函数StartBuffer()的代码如下:
private function StartBuffer(){ @ini_set('zlib.output_compression',0); @ini_set('implicit_flush',1); @ob_end_clean(); @set_time_limit(0); @ob_implicit_flush(1); @ob_start(); }
私有函数EndBuffer()的代码如下:
private function EndBuffer(){ $size = ob_get_length(); header("Content-Length: $size"); header('Connection: close'); ob_flush();ob_implicit_flush(1); }

2
这并没有为已经被接受的答案增加任何内容,甚至没有解决原帖作者的问题。此外,滥用@符号是非常不好的做法。 - GordonM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接