Llamada a función asíncrona en PHP

86

Estoy trabajando en una aplicación web PHP y necesito realizar algunas operaciones de red en la solicitud, como buscar a alguien del servidor remoto según la solicitud del usuario.

¿Es posible simular un comportamiento asincrónico en PHP dado que tengo que pasar algunos datos a una función y también necesito una salida de ella?

Mi código es como:

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

Cada operación de red tarda alrededor de 5 segundos en completarse, agregando un total de 15 segundos al tiempo de respuesta de mi aplicación dado que hago 3 solicitudes.

La función makeNetworkCall () solo realiza una solicitud HTTP POST.

El servidor remoto es una API de terceros, por lo que no tengo ningún control allí.

PD: Por favor no responda dando sugerencias sobre AJAX u otras cosas. Actualmente estoy buscando si puedo hacer esto a través de PHP, puede ser con una extensión de C ++ o algo así.

Hardeep Singh
fuente
Intente usar CURLpara disparar solicitudes y obtener algunos datos de la web ...
Bogdan Burym
Creo que la respuesta se encuentra aquí: stackoverflow.com/questions/13846192/… Nota rápida: use threading
DRAX
posible duplicado de la llamada de subprocesamiento
CRABOLO
Puede utilizar la función stream_select de PHP para ejecutar código sin bloqueo. Reaccionar utiliza esto para crear un bucle dirigido por eventos similares a node.js .
Quinn acusado

Respuestas:

20

Hoy en día, es mejor usar colas que hilos (para aquellos que no usan Laravel, hay toneladas de otras implementaciones como esta ).

La idea básica es que su script PHP original coloca tareas o trabajos en una cola. Luego, tiene trabajadores de trabajos en cola que se ejecutan en otro lugar, sacan trabajos de la cola y comienzan a procesarlos independientemente del PHP original.

Las ventajas son:

  1. Escalabilidad: simplemente puede agregar nodos de trabajo para mantenerse al día con la demanda. De esta forma, las tareas se ejecutan en paralelo.
  2. Fiabilidad: los gestores de colas modernos como RabbitMQ, ZeroMQ, Redis, etc., están hechos para ser extremadamente fiables.
aljo f
fuente
8

No tengo una respuesta directa, pero es posible que desee investigar estas cosas:

Sebastiaan Hilbers
fuente
3

cURL va a ser su única opción real aquí (ya sea eso, o usando sockets sin bloqueo y alguna lógica personalizada).

Este enlace debería enviarle en la dirección correcta. No hay procesamiento asincrónico en PHP, pero si está intentando realizar múltiples solicitudes web simultáneas, cURL multi se encargará de eso por usted.

Colin M
fuente
2

Creo que si el HTML y otras cosas de la interfaz de usuario necesitan que se devuelvan los datos, entonces no habrá una forma de asincronizarlos.

Creo que la única forma de hacer esto en PHP sería registrar una solicitud en una base de datos y hacer una verificación cron cada minuto, o usar algo como el procesamiento de cola de Gearman, o tal vez exec () un proceso de línea de comando

Mientras tanto, su página php tendría que generar algunos html o js que la hacen recargar cada pocos segundos para verificar el progreso, no lo ideal.

Para evitar el problema, ¿cuántas solicitudes diferentes espera? ¿Podría descargarlos todos automáticamente cada hora y guardarlos en una base de datos?

CodeMonkey
fuente
0

Creo que aquí se necesita algo de código sobre la solución cURL, así que compartiré el mío (fue escrito mezclando varias fuentes como el Manual de PHP y comentarios).

Realiza algunas solicitudes HTTP paralelas (dominios en $aURLs) e imprime las respuestas una vez que se completa cada una (y las almacena $donepara otros usos posibles).

El código es más largo de lo necesario debido a la parte de impresión en tiempo real y al exceso de comentarios, pero siéntase libre de editar la respuesta para mejorarla:

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

Una forma es utilizarlo pcntl_fork()en una función recursiva.

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

Una cosa pcntl_fork()es que cuando se ejecuta el script a través de Apache, no funciona (no es compatible con Apache). Entonces, una forma de resolver ese problema es ejecutar el script usando php cli, como: exec('php fork.php',$output);desde otro archivo. Para hacer esto, tendrá dos archivos: uno que está cargado por Apache y otro que se ejecuta exec()desde dentro del archivo cargado por Apache de esta manera:

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
fuente