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
Base de datos: Consejos para acelerar la consulta
Consejos para acelerar la consulta de la base de datos en Laravel
Empresas exitosas: ¿cómo se construyen?
3 señales tempranas de que van a lograr unas empresas exitosas
Elon Musk: 2 reglas para aprender más rápido
Elon Musk: Las 2 reglas para aprender todo de forma más rápida