Asynchroniczne wywołanie funkcji w PHP

86

Pracuję nad aplikacją internetową PHP i muszę wykonać pewne operacje sieciowe w żądaniu, takie jak pobranie kogoś ze zdalnego serwera na podstawie żądania użytkownika.

Czy można symulować zachowanie asynchroniczne w PHP, biorąc pod uwagę, że muszę przekazać pewne dane do funkcji, a także potrzebuję danych wyjściowych z niej.

Mój kod jest taki:

<?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;
?>

Każda operacja sieciowa trwa około 5 sekund, dodając łącznie 15 sekund do czasu odpowiedzi mojej aplikacji, biorąc pod uwagę, że wykonuję 3 żądania.

Funkcja makeNetworkCall () po prostu wykonuje żądanie HTTP POST.

Serwer zdalny jest interfejsem API innej firmy, więc nie mam nad nim żadnej kontroli.

PS: Proszę nie odpowiadać, podając sugestie dotyczące AJAX lub innych rzeczy. Obecnie szukam, czy mogę to zrobić za pomocą PHP, może być z rozszerzeniem C ++ lub czymś podobnym.

Hardeep Singh
źródło
Spróbuj użyć CURLdo odpalenia żądań i pobrania danych z sieci ...
Bogdan Burym
Uważam, że odpowiedź leży tutaj: stackoverflow.com/questions/13846192/ ... Krótka uwaga: użyj wątków
DRAX
Możesz użyć funkcji stream_select PHP, aby uruchomić nieblokujący kod. React odwołuje się do tego, aby stworzyć pętlę zdarzeniami podobną do node.js .
Quinn Comendant

Odpowiedzi:

20

W dzisiejszych czasach lepiej jest używać kolejek niż wątków (dla tych, którzy nie używają Laravel, istnieje mnóstwo innych implementacji, takich jak ta ).

Podstawowa idea jest taka, że ​​Twój oryginalny skrypt PHP umieszcza zadania lub zadania w kolejce. Następnie pracownicy zadań kolejki działają w innym miejscu, usuwają zadania z kolejki i rozpoczynają ich przetwarzanie niezależnie od oryginalnego PHP.

Zalety to:

  1. Skalowalność - wystarczy dodać węzły robocze, aby nadążyć za popytem. W ten sposób zadania są wykonywane równolegle.
  2. Niezawodność - nowoczesne menedżery kolejek, takie jak RabbitMQ, ZeroMQ, Redis itp., Są niezwykle niezawodne.
aljo f
źródło
8

Nie mam bezpośredniej odpowiedzi, ale warto przyjrzeć się tym rzeczom:

Sebastiaan Hilbers
źródło
3

cURL będzie tutaj jedynym prawdziwym wyborem (albo to, albo używając nieblokujących gniazd i niestandardowej logiki).

Ten link powinien skierować cię we właściwym kierunku. W PHP nie ma przetwarzania asynchronicznego, ale jeśli próbujesz wykonać wiele jednoczesnych żądań internetowych, cURL multi zajmie się tym za Ciebie.

Colin M.
źródło
2

Myślę, że jeśli HTML i inne elementy interfejsu użytkownika wymagają zwrócenia danych, nie będzie sposobu na ich asynchronizację.

Uważam, że jedynym sposobem na zrobienie tego w PHP byłoby zarejestrowanie żądania w bazie danych i sprawdzanie crona co minutę lub użycie czegoś takiego jak przetwarzanie kolejki Gearmana lub może exec () proces wiersza poleceń

W międzyczasie strona php musiałaby wygenerować trochę html lub js, co powoduje, że ładuje się co kilka sekund, aby sprawdzić postęp, nie jest to idealne.

Aby obejść ten problem, ile różnych żądań się spodziewasz? Czy możesz je wszystkie automatycznie pobierać mniej więcej co godzinę i zapisywać w bazie danych?

CodeMonkey
źródło
0

Myślę, że potrzebny jest tutaj kod dotyczący rozwiązania cURL, więc podzielę się moim (został napisany z mieszaniem kilku źródeł, takich jak podręcznik PHP i komentarze).

Wykonuje kilka równoległych żądań HTTP (w domenie $aURLs) i drukuje odpowiedzi po zakończeniu każdego z nich (i zapisuje je $donedo innych możliwych zastosowań).

Kod jest dłuższy niż potrzeba, ponieważ część do drukowania w czasie rzeczywistym i nadmiar komentarzy, ale możesz edytować odpowiedź, aby ją ulepszyć:

<?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
?>
Leopoldo Sanczyk
źródło
0

Jednym ze sposobów jest użycie pcntl_fork()w funkcji rekurencyjnej.

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);

Jedną z rzeczy pcntl_fork()jest to, że podczas uruchamiania skryptu przez Apache, nie działa (nie jest obsługiwany przez Apache). Tak więc jednym ze sposobów rozwiązania tego problemu jest uruchomienie skryptu przy użyciu cli php, na przykład: exec('php fork.php',$output);z innego pliku. Aby to zrobić, będziesz mieć dwa pliki: jeden załadowany przez Apache i jeden uruchamiany z exec()wnętrza pliku załadowanego przez Apache w następujący sposób:

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
źródło