Guzzle 7: Envíar solicitudes concurrentes

guzzle 7

Laravel 8 – envía solicitudes concurrentes usando Guzzle 7

El día de hoy trabajaremos en un proyecto en el que estaba enviando ~ 15 solicitudes a API externas, cada una de las cuales tomó ~ 1 s, lo que significa que el tiempo total de solicitud hasta que el usuario recibió una respuesta fue de alrededor de 15 segundos.


Queremos hacer esto más rápido, así que investigamos el envío de estas solicitudes externas al mismo tiempo y pudimos reducir el tiempo de respuesta total a ~ 1 segundo. Este es un ejemplo sobre cómo hacerlo:

  • Crearemos un proyecto nuevo:
laravel new concurrent_requests_example
  • Usaremos Guzzle 7 para enviar las solicitudes:
composer require guzzlehttp/guzzle
  • Haremos un controlador:
php artisan make:Controller ExampleController
  • Para comparar la diferencia entre solicitudes secuenciales y concurrentes, haremos un punto final para cada una de ellas:
class ExampleController extends Controller
{
    /**
     * Sends requests sequentially
     */
    public function sequential()
    {    }    /**
     * Sends requests concurrently
     */
    public function concurrent()
    {    }
}
  • Y las rutas que pondremos en web.php:
/* laravel 8 syntax */
Route::get('/sequential', [ExampleController::class, 'sequential']);
Route::get('/concurrent', [ExampleController::class, 'concurrent']);/* pre-laravel 8 syntax */
Route::get('/sequential', 'ExampleController@sequential');
Route::get('/concurrent', 'ExampleController@concurrent');
  • Ahora buscamos 10 apis públicas al azar y ponemos solicitudes secuenciales en nuestro método secuencial ():
/**
 * Sends requests sequentially
 */
public function sequential()
{
    $time_start = microtime(true);    $client = new Client();    $responses = [];    $responses['archive'] = $client->get('<https://archive.org/advancedsearch.php?q=subject:google+sheets&output=json>');
    $responses['cat_facts'] = $client->get('<https://cat-fact.herokuapp.com/facts>');
    $responses['coin_gecko'] = $client->get('<https://api.coingecko.com/api/v3/exchange_rates>');
    $responses['universities'] = $client->get('<http://universities.hipolabs.com/search?country=United+Kingdom>');
    $responses['countries'] = $client->get('<https://restcountries.eu/rest/v2/all>');
    $responses['randomuser'] = $client->get('<https://randomuser.me/api/>');
    $responses['punkapi'] = $client->get('<https://api.punkapi.com/v2/beers>');
    $responses['publicapis'] = $client->get('<https://api.publicapis.org/entries>');
    $responses['openlibrary'] = $client->get('<https://openlibrary.org/api/volumes/brief/isbn/9780525440987.json>');
    $responses['food_facts'] = $client->get('<https://world.openfoodfacts.org/api/v0/product/737628064502.json>');    foreach ($responses as $key => $result)
    {
        echo $key . "<br>";        //$result is a Guzzle Response object
        //do stuff with $result
        //eg. $result->getBody()
    }    echo 'Sequential execution time in seconds: ' . (microtime(true) - $time_start);
}
  • Y para el método concurrente () utilizamos los mismos puntos finales:
/**
 * Sends requests concurrently
 */
public function concurrent()
{
    $time_start = microtime(true);    $client = new Client();    $promises = [
        'archive' => $client->getAsync('<https://archive.org/advancedsearch.php?q=subject:google+sheets&output=json>'),
        'cat_facts'   => $client->getAsync('<https://cat-fact.herokuapp.com/facts>'),
        'coin_gecko'  => $client->getAsync('<https://api.coingecko.com/api/v3/exchange_rates>'),
        'universities'  => $client->getAsync('<http://universities.hipolabs.com/search?country=United+Kingdom>'),
        'countries'  => $client->getAsync('<https://restcountries.eu/rest/v2/all>'),
        'randomuser'  => $client->getAsync('<https://randomuser.me/api/>'),
        'punkapi'  => $client->getAsync('<https://api.punkapi.com/v2/beers>'),
        'publicapis'  => $client->getAsync('<https://api.publicapis.org/entries>'),
        'openlibrary'  => $client->getAsync('<https://openlibrary.org/api/volumes/brief/isbn/9780525440987.json>'),
        'food_facts'  => $client->getAsync('<https://world.openfoodfacts.org/api/v0/product/737628064502.json>'),
    ];    $responses = Promise\\Utils::settle($promises)->wait();    foreach ($responses as $key => $response)
    {
        echo $key . "<br>";        //response state is either 'fulfilled' or 'rejected'
        if($response['state'] === 'rejected')
        {
            //handle rejected
            continue;
        }        //$result is a Guzzle Response object
        $result = $response['value'];        //do stuff with $result
        //eg. $result->getBody()
    }    echo 'Concurrent execution time in seconds: ' . (microtime(true) - $time_start);
}
  • Salida secuencial:
archive
cat_facts
coin_gecko
universities
countries
randomuser
punkapi
publicapis
openlibrary
food_facts
Sequential execution time in seconds: 19.31148982048
  • Salida concurrente:
archive
cat_facts
coin_gecko
countries
food_facts
openlibrary
publicapis
punkapi
randomuser
universities
Concurrent execution time in seconds: 4.8118050098419

Como puede ver, hay un ahorro de tiempo significativo de unos 15 segundos. Cuantas más solicitudes tenga, más significativa será la brecha.

¡Ojalá esto ayude a alguien!

Recent Post