前言
在做PHP开发过程中,时常会遇到需要处理一些耗时任务的情况:例如发送大量邮件,大量数据库查询,导出数据量大的Excel等。在处理耗时、资源密集型任务时,不少PHP开发人员倾向于设置max_execution_time来延长php执行时间。除了该方法外,也可以通过消息队列来处理,但比较复杂。本文介绍两种简单的方法,通过将长时间运行的任务与主请求流分离来改善应用程序的用户体验。
思路
在处理发送邮件,短信等耗时任务时,可以在发送邮件或短信前,创建一个单独的子进程去发送,主进程立即返回,这样无需等待发送结果。同理,在需要下载大数据量的Excel文档时,也通过子进程生成excel文件到服务器的指定目录,最后返回一个文件的下载地址给用户(把下载地址存到缓存或数据库中),用户点击直接下载即可。
方法一:pcntl_fork
简介
PHP是支持并发的,只是平时很少使用。创建进程需要使用php的一个函数pcntl_fork()
,Linux下有个叫fork()的函数,用来创建子进程。这个函数和Linux下这个函数类似。需要注意的是,这个函数在Linux下才能使用,而且需要安装pcntl的扩展。
函数的具体使用可以查阅官方文档:http://php.net/manual/zh/function.pcntl-fork.php
示例
<?php
$pid = pcntl_fork();
//父进程和子进程都会执行下面代码
if ($pid == -1) {
//错误处理:创建子进程失败时返回-1.
die('could not fork');
} else if ($pid) {
//父进程会得到子进程号,所以这里是父进程执行的逻辑
//发送邮件等,这里使用sleep模拟
sleep(10);
error_log($pid.'---'.time()."\r\n",3,'1.txt');
pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。
} else {
//子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
echo 'child';
}
方法二:fastcgi_finish_request
简介
当PHP运行在FastCGI模式时,PHP FPM提供了一个名为fastcgi_finish_request
的方法.按照文档上的说法,此方法可以提高请求的处理速度,如果有些处理可以在页面生成完后再进行,就可以使用这个方法。
介绍地址:https://www.laruence.com/2011/04/13/1991.html
示例
echo '例子:';
file_put_contents('log.txt', date('Y-m-d H:i:s') . " 上传视频\n", FILE_APPEND);
fastcgi_finish_request();
sleep(10);
file_put_contents('log.txt', date('Y-m-d H:i:s') . " 转换格式\n", FILE_APPEND);
sleep(20);
file_put_contents('log.txt', date('Y-m-d H:i:s') . " 提取图片\n", FILE_APPEND);
sleep(30);
file_put_contents('log.txt', date('Y-m-d H:i:s') . " 结束\n", FILE_APPEND);
方法三:Process组件
简介
Symfony
的Process
组件用于在子进程中执行命令,其原理是使用PHP的proc_open
函数来运行进程。支持Windows和Linux系统。
官网地址:https://github.com/symfony/process
注意:如果你的php是通过宝塔安装的,默认会禁用proc_open
和proc_get_status
函数,需要在禁用函数里去掉。
安装
composer require symfony/process
示例
新建cli.php
,内容如下:
<?php
//模拟耗时任务,10秒后,在log.txt中输出当前时间
sleep(10);
error_log(time()."\r\n",3,'log.txt');
echo time();die;
新建index.php
,内容如下:
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
$process = new Process(['php','cli.php']); //等同于在命令行窗口执行:php cli.php
$process->setOptions(['create_new_console' => true]);
$process->start();
echo 'ok';
注:确保php.exe已添加到系统环境变量中(用户环境变量不起作用),或者输入php.exe的完整路径。
在浏览器中访问index.php,程序会立即显示ok,不会等待,10秒后,程序目录会自动生成一个log.txt文件,代表耗时任务已执行完成。
程序执行后会在C:\Windows\Temp
目录下生成sf_proc_00.out
和sf_proc_00.err
文件,如果程序没有执行,可以打开sf_proc_00.err
文件查看错误原因。
总结
通过利用 PHP 生成在后台运行的单独进程的能力,主脚本将更快地响应用户操作。因此,它可以很好的提高用户体验,而不是让用户等待很长时间(没有反馈)才能完成请求。
DEMO
参考:
https://www.toptal.com/laravel/handling-intensive-tasks-with-laravel