Асинхронный вызов функции в PHP

86

Я работаю над веб-приложением PHP, и мне нужно выполнить некоторые сетевые операции в запросе, например, получить кого-то с удаленного сервера на основе запроса пользователя.

Можно ли смоделировать асинхронное поведение в 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.

Удаленный сервер - это сторонний API, поэтому я не могу его контролировать.

PS: Пожалуйста, не отвечайте на предложения по AJAX или другим вещам. В настоящее время я ищу, могу ли я сделать это через PHP, может быть с расширением C ++ или чем-то в этом роде.

Хардип Сингх
источник
Попробуйте использовать CURLдля
запуска
Я считаю, что ответ находится здесь: stackoverflow.com/questions/13846192/… Краткое примечание: используйте многопоточность
DRAX
возможный дубликат вызова потоковой
CRABOLO
Вы можете использовать функцию PHP stream_select для запуска неблокирующего кода. Реагировать использует это , чтобы создать петлю , управляемые события , похожую на Node.js .
Куинн Комендант

Ответы:

20

В настоящее время лучше использовать очереди, чем потоки (для тех, кто не использует Laravel, существует множество других подобных реализаций ).

Основная идея заключается в том, что ваш исходный сценарий PHP помещает задачи или задания в очередь. Затем у вас есть рабочие задания очереди, работающие в другом месте, берущие задания из очереди и начинающие их обрабатывать независимо от исходного PHP.

Преимущества:

  1. Масштабируемость - вы можете просто добавить рабочие узлы, чтобы не отставать от спроса. Таким образом, задачи выполняются параллельно.
  2. Надежность - современные менеджеры очередей, такие как RabbitMQ, ZeroMQ, Redis и т. Д., Сделаны очень надежными.
альджо ф
источник
8

У меня нет прямого ответа, но вы, возможно, захотите изучить эти вещи:

  • Проект отдачи - https://github.com/recoilphp/recoil
  • расширение LibEvent php? http://www.php.net/manual/en/book.libevent.php
  • разветвление процесса http://www.php.net/manual/en/function.pcntl-fork.php
  • брокеры сообщений, то есть вы можете запустить воркеры для выполнения HTTP-вызовов, и как только это jobбудет сделано, вставьте новое задание, описывающее работу, которая должна быть выполнена для обработки кэшированного тела HTTP-ответа.
Себастьян Хильберс
источник
3

cURL будет вашим единственным реальным выбором здесь (либо это, либо использование неблокирующих сокетов и некоторой пользовательской логики).

Эта ссылка должна направить вас в правильном направлении. В PHP нет асинхронной обработки, но если вы пытаетесь сделать несколько одновременных веб-запросов, cURL multi позаботится об этом за вас.

Колин М
источник
2

Я думаю, что если HTML и другие элементы пользовательского интерфейса нуждаются в возвращаемых данных, тогда не будет способа их асинхронизировать.

Я считаю, что единственный способ сделать это в PHP - это зарегистрировать запрос в базе данных и каждую минуту проверять cron, или использовать что-то вроде обработки очереди Gearman, или, возможно, exec () процесс командной строки

Тем временем ваша php-страница должна будет сгенерировать некоторые html или js, которые заставят ее перезагружаться каждые несколько секунд для проверки прогресса, что не идеально.

Чтобы обойти проблему, сколько разных запросов вы ожидаете? Могли бы вы загружать их все автоматически каждый час или около того и сохранять в базу данных?

CodeMonkey
источник
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
?>
Леопольдо Санчик
источник
0

Один из способов - использовать 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);
JVE999
источник