99

我正在开发一个 PHP Web 应用程序,我需要在请求中执行一些网络操作,例如根据用户的请求从远程服务器获取某人。

考虑到我必须将一些数据传递给函数并且还需要从中输出,是否可以在 PHP 中模拟异步行为。

我的代码是这样的:

<?php

     $data1 = processGETandPOST();
     $data2 = processGETandPOST();
     $data3 = processGETandPOST();

     $response1 = makeNetworkCall($data1);
     $response2 = makeNetworkCall($data2);
     $response3 = makeNetworkCall($data3);

     processNetworkResponse($response1);
     processNetworkResponse($response2);
     processNetworkResponse($response3);

     /*HTML and OTHER UI STUFF HERE*/

     exit;
?>

每个网络操作大约需要 5 秒才能完成,给我的应用程序的响应时间增加了 15 秒,因为我提出了 3 个请求。

makeNetworkCall() 函数只是做一个 HTTP POST 请求。

远程服务器是第 3 方 API,所以我无法控制那里。

PS:请不要回答有关AJAX或其他事情的建议。我目前正在寻找是否可以通过 PHP 做到这一点,可能使用 C++ 扩展或类似的东西。

4

12 回答 12

29

如今,使用队列比使用线程更好(对于那些不使用 Laravel 的人来说,还有很多其他类似的实现)。

基本思想是,您的原始 PHP 脚本将任务或作业放入队列中。然后,您让队列作业工作者在其他地方运行,将作业从队列中取出并开始独立于原始 PHP 处理它们。

优点是:

  1. 可扩展性——您可以添加工作节点来满足需求。这样,任务是并行运行的。
  2. 可靠性——现代队列管理器,如 RabbitMQ、ZeroMQ、Redis 等,都非常可靠。
于 2017-06-03T04:23:09.530 回答
9

我没有直接的答案,但你可能想看看这些事情:

于 2014-05-05T08:05:46.440 回答
8

这个老问题有了新的答案。现在有一些 PHP 的“异步”解决方案(相当于 Python 的多进程,因为它们产生新的独立 PHP 进程,而不是在框架级别管理它)

我见过的两个解决方案是

试试看!

于 2020-12-16T12:02:24.690 回答
4

cURL 将是您在这里唯一真正的选择(或者使用非阻塞套接字和一些自定义逻辑)。

此链接应该会向您发送正确的方向。PHP 中没有异步处理,但如果您尝试同时发出多个 Web 请求,cURL multi 会为您处理。

于 2013-01-09T13:34:38.993 回答
4

一种方法是pcntl_fork()在递归函数中使用。

function networkCall(){
  $data = processGETandPOST();
  $response = makeNetworkCall($data);
  processNetworkResponse($response);
  return true;
}

function runAsync($times){
  $pid = pcntl_fork();
  if ($pid == -1) {
    die('could not fork');
  } else if ($pid) {
    // we are the parent
    $times -= 1;
    if($times>0)
      runAsync($times);
    pcntl_wait($status); //Protect against Zombie children
  } else {
    // we are the child
    networkCall();
    posix_kill(getmypid(), SIGKILL);
  }
}

runAsync(3);

一件事pcntl_fork()是,当通过 Apache 运行脚本时,它不起作用(Apache 不支持它)。因此,解决该问题的一种方法是使用 php cli 运行脚本,例如:exec('php fork.php',$output);从另一个文件。为此,您将拥有两个文件:一个由 Apache 加载,另一个在exec()Apache 加载的文件中运行,如下所示:

apacheLoadedFile.php

exec('php fork.php',$output);

fork.php

function networkCall(){
  $data = processGETandPOST();
  $response = makeNetworkCall($data);
  processNetworkResponse($response);
  return true;
}

function runAsync($times){
  $pid = pcntl_fork();
  if ($pid == -1) {
    die('could not fork');
  } else if ($pid) {
    // we are the parent
    $times -= 1;
    if($times>0)
      runAsync($times);
    pcntl_wait($status); //Protect against Zombie children
  } else {
    // we are the child
    networkCall();
    posix_kill(getmypid(), SIGKILL);
  }
}

runAsync(3);
于 2020-03-19T17:57:46.603 回答
3

我认为如果 HTML 和其他 UI 内容需要返回的数据,那么就不会有异步它的方法。

我相信在 PHP 中做到这一点的唯一方法是在数据库中记录一个请求并每分钟进行一次 cron 检查,或者使用类似 Gearman 队列处理的东西,或者可能是 exec() 一个命令行进程

与此同时,您的 php 页面必须生成一些 html 或 js,使其每隔几秒钟重新加载一次以检查进度,这并不理想。

为了回避这个问题,您期望有多少不同的请求?你能每隔一小时左右自动下载它们并保存到数据库吗?

于 2013-01-09T14:13:13.520 回答
0

还有 http v2,它是 curl 的包装器。可以通过pecl安装。

http://devel-m6w6.rhcloud.com/mdref/http/

于 2014-06-16T22:38:54.260 回答
0

有一种方法可以调用 php 函数异步。

  1. 将类、方法和参数序列化到文件中

    $callback = [ 'class' => $class//必须是字符串类名,'method' => $method//必须是字符串方法名,'params' => $params, ]; file_put_contents($file, addlashes(serialize($callback)) . "\n");

     //call async.php file like
     shell_exec('php async.php >> /tmp/log/async.std &');
    
  2. 制作序列化数据调用者 - 假设名称是 async.php

    $fileContent = file_get_contents($file); $callback = unserialize(stripslashes($serialized_row));

  3. 使类方法调用者

    $callback_class = $callback['class']; $callback_method = $callback['方法']; $callback_params = (array)$callback['params'];

         $reflection = new \ReflectionClass($callback_class);
         $is_static = (new \ReflectionMethod($callback_class, $callback_method))->isStatic();
         if ($is_static) {
    
             call_user_func_array([$callback_class, $callback_method], $callback_params);
         }
         elseif ($callback_method == '__construct') {
    
             $reflection->newInstanceArgs($callback_params);
         }
         else {
    
             $reflectionMethod = new \ReflectionMethod($callback_class, $callback_method);
             $reflectionMethod->invokeArgs(new $callback_class(), $callback_params);
         }
    
于 2021-12-12T14:33:40.130 回答
0

不能直接通过php进行异步处理,但可以通过系统规划机制间接进行处理。您可以使用 crontab。如果您将进程发送到 crontab,它可以为您安排好它。

于 2021-05-14T13:58:01.993 回答
0

我认为这里需要一些关于 cURL 解决方案的代码,所以我将分享我的(它是混合了几个来源作为 PHP 手册和评论编写的)。

它执行一些并行 HTTP 请求(域中的域$aURLs),并在每个请求完成后打印响应(并将它们存储起来以$done供其他可能的用途)。

由于实时打印部分和过多的注释,代码比需要的要长,但请随时编辑答案以改进它:

<?php
/* Strategies to avoid output buffering, ignore the block if you don't want to print the responses before every cURL is completed */
ini_set('output_buffering', 'off'); // Turn off output buffering
ini_set('zlib.output_compression', false); // Turn off PHP output compression       
//Flush (send) the output buffer and turn off output buffering
ob_end_flush(); while (@ob_end_flush());        
apache_setenv('no-gzip', true); //prevent apache from buffering it for deflate/gzip
ini_set('zlib.output_compression', false);
header("Content-type: text/plain"); //Remove to use HTML
ini_set('implicit_flush', true); // Implicitly flush the buffer(s)
ob_implicit_flush(true);
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
$string=''; for($i=0;$i<1000;++$i){$string.=' ';} output($string); //Safari and Internet Explorer have an internal 1K buffer.
//Here starts the program output

function output($string){
    ob_start();
    echo $string;
    if(ob_get_level()>0) ob_flush();
    ob_end_clean();  // clears buffer and closes buffering
    flush();
}

function multiprint($aCurlHandles,$print=true){
    global $done;
    // iterate through the handles and get your content
    foreach($aCurlHandles as $url=>$ch){
        if(!isset($done[$url])){ //only check for unready responses
            $html = curl_multi_getcontent($ch); //get the content           
            if($html){
                $done[$url]=$html;
                if($print) output("$html".PHP_EOL);
            }           
        }
    }
};

function full_curl_multi_exec($mh, &$still_running) {
    do {
      $rv = curl_multi_exec($mh, $still_running); //execute the handles 
    } while ($rv == CURLM_CALL_MULTI_PERFORM); //CURLM_CALL_MULTI_PERFORM means you should call curl_multi_exec() again because there is still data available for processing
    return $rv;
} 

set_time_limit(60); //Max execution time 1 minute

$aURLs = array("http://domain/script1.php","http://domain/script2.php");  // array of URLs

$done=array();  //Responses of each URL

    //Initialization
    $aCurlHandles = array(); // create an array for the individual curl handles
    $mh = curl_multi_init(); // init the curl Multi and returns a new cURL multi handle
    foreach ($aURLs as $id=>$url) { //add the handles for each url        
        $ch = curl_init(); // init curl, and then setup your options
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // returns the result - very important
        curl_setopt($ch, CURLOPT_HEADER, 0); // no headers in the output
        $aCurlHandles[$url] = $ch;
        curl_multi_add_handle($mh,$ch);
    }

    //Process
    $active = null; //the number of individual handles it is currently working with
    $mrc=full_curl_multi_exec($mh, $active); 
    //As long as there are active connections and everything looks OK…
    while($active && $mrc == CURLM_OK) { //CURLM_OK means is that there is more data available, but it hasn't arrived yet.  
        // Wait for activity on any curl-connection and if the network socket has some data…
        if($descriptions=curl_multi_select($mh,1) != -1) {//If waiting for activity on any curl_multi connection has no failures (1 second timeout)     
            usleep(500); //Adjust this wait to your needs               
            //Process the data for as long as the system tells us to keep getting it
            $mrc=full_curl_multi_exec($mh, $active);        
            //output("Still active processes: $active".PHP_EOL);        
            //Printing each response once it is ready
            multiprint($aCurlHandles);  
        }
    }

    //Printing all the responses at the end
    //multiprint($aCurlHandles,false);      

    //Finalize
    foreach ($aCurlHandles as $url=>$ch) {
        curl_multi_remove_handle($mh, $ch); // remove the handle (assuming  you are done with it);
    }
    curl_multi_close($mh); // close the curl multi handler
?>
于 2019-06-29T05:39:24.547 回答
0

如果您不希望它成为后台进程,最简单的解决方案是proc_open(). 它执行终端命令而不等待它们完成。你可以用它来调用你的脚本;像这样的东西:

proc_open('start cmd.exe php path-to-script.php', [], $pipes);

您还可以让它使用-r标志运行内联 php 代码。阅读 PHP 命令的帮助以查看所有可用的标志。


另一种方法是向自己发送 HTTP 请求,忽略超时警告!您可以使用 php 简单地实现这个想法file_get_contents()

$stream_context = stream_context_create(['http'=>['timeout' => 1]]);
file_get_contents('http://localhost/script-to-run-async.php', false, $stream_context);

// Replace "http://localhost/path-to-script.php" with
// a script that should be run asynchronously.

毕竟,您可以使用此答案忽略超时返回的警告。

于 2021-09-03T07:43:54.003 回答
0

这是一篇相当老的帖子,但仍然可以帮助使用 Magento 1、Magento 2 和 PHP 的人,这里有几个答案。

如果您使用的是Magento 1,则可以通过设置 cron 作业或通过队列管理代码来轻松完成异步运行代码,就像消息队列一样,这又需要 cron 设置。

如果您使用的是Magento 2,那么这取决于您使用的是哪个版本的 Magento 2,

  1. 对于早于 2.3.3 的版本,您可以参考https://devdocs.magento.com/guides/v2.4/extension-dev-guide/message-queues/async-message-queue-config-files.html
  2. 对于 2.3.3 之后的版本,您可以参考https://devdocs.magento.com/guides/v2.4/rest/asynchronous-web-endpoints.htmlhttps://devdocs.magento.com/guides/v2.4 /extension-dev-guide/async-operations.html

如果您想使用 PHP,或者上述参考资料都不适合您,或者您觉得需要更简单的方法,那么您可以参考https://www.php.net/manual/en/function.shell-exec。 php#118495

例子:

<?php
    
namespace Vendor\Module\Controller\ControllerNameFolder;

class YourCustomAsyncAction extends \Magento\Framework\App\Action\Action
{
    /**
     * resultJsonFactory
     *
     * @var \Magento\Framework\Controller\Result\JsonFactory
     */
    protected $resultJsonFactory;    
    /**
     * _urlInterface
     *
     * @var \Magento\Framework\UrlInterface
     */
    protected $_urlInterface;
        
    /**
     * __construct
     *
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
     * @return void
     */
    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
        \Magento\Framework\UrlInterface $urlInterface
    )
    {
        parent::__construct($context);
        $this->resultJsonFactory = $resultJsonFactory;    
        $this->_urlInterface = $urlInterface;
        $this->location = $location;
    }
    
    public function execute()
    {
        $result = $this->resultJsonFactory->create();
        $params = $this->getRequest()->getParams();
        /* prepare your URL */    
        $url = $this->_urlInterface->getUrl('module_route/controllername/actionname',$params);
        /* prepare your URL */

        /* async code */
        shell_exec("wget $url>/dev/null >/dev/null &");
        /* async code */
        
        return $result->setData(['success'=>true]);
    }
}
于 2021-07-08T13:49:24.563 回答