dav*_*avr 136 php queue asynchronous background task
我在一个有点大的Web应用程序上工作,后端主要是在PHP中.代码中有几个地方我需要完成一些任务,但我不想让用户等待结果.例如,在创建新帐户时,我需要向他们发送欢迎电子邮件.但是当他们点击"完成注册"按钮时,我不想让他们等到实际发送电子邮件,我只想启动该过程,并立即向用户返回消息.
到目前为止,在某些地方,我一直在使用exec()感觉像是一个黑客.基本上做的事情如下:
exec("doTask.php $arg1 $arg2 $arg3 >/dev/null 2>&1 &");
Run Code Online (Sandbox Code Playgroud)
这似乎有效,但我想知道是否有更好的方法.我正在考虑编写一个在MySQL表中排队任务的系统,以及一个单独的长时间运行的PHP脚本,每秒查询一次该表,并执行它找到的任何新任务.如果需要的话,这也有可能让我将来在几台工作机器之间拆分任务.
我是在重新发明轮子吗?有没有比exec()hack或MySQL队列更好的解决方案?
Pau*_*xon 76
我已经使用了排队方法,并且它可以正常工作,因为您可以推迟处理,直到您的服务器负载空闲,如果您可以轻松地分割"非紧急的任务",则可以非常有效地管理负载.
滚动你自己并不是太棘手,这里还有一些其他的选择:
另一个也许更简单的方法是使用ignore_user_abort - 一旦你将页面发送给用户,你就可以进行最后的处理而不用担心提前终止,尽管这会产生延长用户页面负载的效果.透视.
小智 22
当您只想执行一个或多个HTTP请求而不必等待响应时,还有一个简单的PHP解决方案.
在调用脚本中:
$socketcon = fsockopen($host, 80, $errno, $errstr, 10);
if($socketcon) {
$socketdata = "GET $remote_house/script.php?parameters=... HTTP 1.1\r\nHost: $host\r\nConnection: Close\r\n\r\n";
fwrite($socketcon, $socketdata);
fclose($socketcon);
}
// repeat this with different parameters as often as you like
Run Code Online (Sandbox Code Playgroud)
在被调用的script.php上,您可以在第一行中调用这些PHP函数:
ignore_user_abort(true);
set_time_limit(0);
Run Code Online (Sandbox Code Playgroud)
这会导致脚本在HTTP连接关闭时无时间限制地继续运行.
roj*_*oca 17
fork进程的另一种方法是通过curl.您可以将内部任务设置为Web服务.例如:
然后在您的用户访问的脚本中调用该服务:
$service->addTask('t1', $data); // post data to URL via curl
Run Code Online (Sandbox Code Playgroud)
您的服务可以使用mysql或您喜欢的任何内容跟踪任务队列:它全部包含在服务中,而您的脚本只是在使用URL.如果需要,这可以让您将服务移动到另一台机器/服务器(即可轻松扩展).
添加http授权或自定义授权方案(如亚马逊的Web服务)可让您打开由其他人/服务(如果需要)使用的任务,并且您可以进一步使用并在顶部添加监控服务以跟踪队列和任务状态.
它确实需要一些设置工作,但有很多好处.
我已经将Beanstalkd用于一个项目,并计划再次使用.我发现它是运行异步进程的绝佳方式.
我用它完成的一些事情是:
我编写了一个基于Zend-Framework的系统来解码一个"漂亮的"url,例如,调整它调用的图像的大小QueueTask('/image/resize/filename/example.jpg').首先将URL解码为数组(模块,控制器,操作,参数),然后转换为JSON以注入队列本身.
一个长时间运行的cli脚本然后从队列中获取作业,运行它(通过Zend_Router_Simple),如果需要,将信息放入memcached中,以便PHP完成时根据需要获取网站.
我还提到的一个问题是cli-script在重新启动之前只运行了50个循环,但是如果它确实想要按计划重启,它会立即执行(通过bash脚本运行).如果出现问题并且我做了exit(0)(默认值为exit;或die();),它将首先暂停几秒钟.
小智 7
如果只是提供昂贵任务的问题,如果支持php-fpm,为什么不使用fastcgi_finish_request()函数?
此函数将所有响应数据刷新到客户端并完成请求.这允许执行耗时的任务而不会断开与客户端的连接.
你并没有真正以这种方式使用异步性:
fastcgi_finish_request().再次需要php-fpm.
这是我为我的Web应用程序编写的一个简单类.它允许分支PHP脚本和其他脚本.适用于UNIX和Windows.
class BackgroundProcess {
static function open($exec, $cwd = null) {
if (!is_string($cwd)) {
$cwd = @getcwd();
}
@chdir($cwd);
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
$WshShell = new COM("WScript.Shell");
$WshShell->CurrentDirectory = str_replace('/', '\\', $cwd);
$WshShell->Run($exec, 0, false);
} else {
exec($exec . " > /dev/null 2>&1 &");
}
}
static function fork($phpScript, $phpExec = null) {
$cwd = dirname($phpScript);
@putenv("PHP_FORCECLI=true");
if (!is_string($phpExec) || !file_exists($phpExec)) {
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
$phpExec = str_replace('/', '\\', dirname(ini_get('extension_dir'))) . '\php.exe';
if (@file_exists($phpExec)) {
BackgroundProcess::open(escapeshellarg($phpExec) . " " . escapeshellarg($phpScript), $cwd);
}
} else {
$phpExec = exec("which php-cli");
if ($phpExec[0] != '/') {
$phpExec = exec("which php");
}
if ($phpExec[0] == '/') {
BackgroundProcess::open(escapeshellarg($phpExec) . " " . escapeshellarg($phpScript), $cwd);
}
}
} else {
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
$phpExec = str_replace('/', '\\', $phpExec);
}
BackgroundProcess::open(escapeshellarg($phpExec) . " " . escapeshellarg($phpScript), $cwd);
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
106203 次 |
| 最近记录: |