Asynchroner Funktionsaufruf in PHP

86

Ich arbeite an einer PHP-Webanwendung und muss einige Netzwerkoperationen in der Anforderung ausführen, z. B. das Abrufen einer Person vom Remote-Server basierend auf der Benutzeranforderung.

Ist es möglich, asynchrones Verhalten in PHP zu simulieren, da ich einige Daten an eine Funktion übergeben muss und auch eine Ausgabe von ihr benötige?

Mein Code lautet wie folgt:

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

Jeder Netzwerkvorgang dauert ungefähr 5 Sekunden, um die Antwortzeit meiner Anwendung um insgesamt 15 Sekunden zu verlängern, vorausgesetzt, ich stelle 3 Anfragen.

Die Funktion makeNetworkCall () führt lediglich eine HTTP-POST-Anforderung aus.

Der Remote-Server ist eine Drittanbieter-API, daher habe ich dort keine Kontrolle darüber.

PS: Bitte antworten Sie nicht mit Vorschlägen zu AJAX oder anderen Dingen. Ich suche derzeit, ob ich dies über PHP tun kann, möglicherweise mit einer C ++ - Erweiterung oder so ähnlich.

Hardeep Singh
quelle
Versuchen Sie CURL, Anfragen zu feuern und einige Daten aus dem Internet abzurufen ...
Bogdan Burym
Ich glaube, dass die Antwort hier liegt: stackoverflow.com/questions/13846192/… Kurzer Hinweis: Threading verwenden
DRAX
Mögliches Duplikat des PHP-Threading-Aufrufs einer PHP-Funktion asynchron
CRABOLO
Sie können die stream_select- Funktion von PHP verwenden , um nicht blockierenden Code auszuführen. React verwendet dies, um eine ereignisgesteuerte Schleife ähnlich der Datei node.js zu erstellen .
Quinn Comendant

Antworten:

20

Heutzutage ist es besser, Warteschlangen als Threads zu verwenden (für diejenigen, die Laravel nicht verwenden, gibt es unzählige andere Implementierungen wie diese ).

Die Grundidee ist, dass Ihr ursprüngliches PHP-Skript Aufgaben oder Jobs in eine Warteschlange stellt. Dann haben Sie Warteschlangen-Job-Worker, die an anderer Stelle ausgeführt werden, Jobs aus der Warteschlange entfernen und sie unabhängig vom ursprünglichen PHP verarbeiten.

Die Vorteile sind:

  1. Skalierbarkeit - Sie können einfach Arbeitsknoten hinzufügen, um mit der Nachfrage Schritt zu halten. Auf diese Weise werden Aufgaben parallel ausgeführt.
  2. Zuverlässigkeit - Moderne Warteschlangenmanager wie RabbitMQ, ZeroMQ, Redis usw. sind äußerst zuverlässig.
aljo f
quelle
8

Ich habe keine direkte Antwort, aber vielleicht möchten Sie diese Dinge untersuchen:

Sebastiaan Hilbers
quelle
3

cURL wird hier Ihre einzige echte Wahl sein (entweder das oder die Verwendung von nicht blockierenden Sockets und einer benutzerdefinierten Logik).

Dieser Link sollte Sie in die richtige Richtung schicken. In PHP gibt es keine asynchrone Verarbeitung. Wenn Sie jedoch versuchen, mehrere Webanforderungen gleichzeitig zu stellen, übernimmt cURL multi dies für Sie.

Colin M.
quelle
2

Ich denke, wenn der HTML-Code und andere UI-Inhalte die zurückgegebenen Daten benötigen, gibt es keine Möglichkeit, sie zu asynchronisieren.

Ich glaube, der einzige Weg, dies in PHP zu tun, wäre, eine Anfrage in einer Datenbank zu protokollieren und jede Minute eine Cron-Überprüfung durchführen zu lassen oder etwas wie die Gearman-Warteschlangenverarbeitung zu verwenden oder vielleicht einen Befehlszeilenprozess auszuführen

In der Zwischenzeit müsste Ihre PHP-Seite HTML oder JS generieren, sodass sie alle paar Sekunden neu geladen wird, um den Fortschritt zu überprüfen. Dies ist nicht ideal.

Wie viele verschiedene Anfragen erwarten Sie, um das Problem zu umgehen? Könnten Sie sie alle etwa jede Stunde automatisch herunterladen und in einer Datenbank speichern?

CodeMonkey
quelle
0

Ich denke, hier wird Code über die cURL-Lösung benötigt, daher werde ich meinen teilen (er wurde geschrieben, indem mehrere Quellen als PHP-Handbuch und Kommentare gemischt wurden).

Es führt einige parallele HTTP-Anforderungen (Domains in $aURLs) aus und druckt die Antworten aus, sobald jede abgeschlossen ist (und speichert sie $donefür andere mögliche Zwecke).

Der Code ist länger als erforderlich, da der Echtzeit-Druckteil und der Überschuss an Kommentaren vorhanden sind. Sie können die Antwort jedoch jederzeit bearbeiten, um sie zu verbessern:

<?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
quelle
0

Eine Möglichkeit ist die Verwendung pcntl_fork()in einer rekursiven Funktion.

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

Eine Sache pcntl_fork()ist, dass das Ausführen des Skripts über Apache nicht funktioniert (es wird von Apache nicht unterstützt). Eine Möglichkeit, dieses Problem zu beheben, besteht darin, das Skript mit der PHP-CLI auszuführen, z. B.: exec('php fork.php',$output);Aus einer anderen Datei. Dazu haben Sie zwei Dateien: eine, die von Apache geladen wird, und eine, die exec()aus der von Apache geladenen Datei ausgeführt wird:

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
quelle