Documentación de Laravel 10.x
Aquí encontrarás fragmentos de código de Laravel y consejos útiles sobre desarrollo web.
Al construir tu aplicación web, es posible que tengas algunas tareas, como analizar y almacenar un archivo CSV cargado, que tardan demasiado en realizarse durante una solicitud web típica. Afortunadamente, Laravel te permite crear fácilmente trabajos en cola que se pueden procesar en segundo plano. Al mover tareas intensivas en tiempo a una cola, tu aplicación puede responder a las solicitudes web con una velocidad impresionante y brindar una mejor experiencia al usuario.
Las colas de Laravel proporcionan una API de colas unificada en una variedad de backends de cola diferentes, como Amazon SQS, Redis o incluso una base de datos relacional.
Las opciones de configuración de la cola de Laravel se almacenan en el archivo de configuración config/queue.php
de tu aplicación. En este archivo, encontrarás configuraciones de conexión para cada uno de los controladores de cola que se incluyen con el marco, incluidos los controladores de base de datos, Amazon SQS, Redis y Beanstalkd, así como un controlador síncrono que ejecutará trabajos inmediatamente (para su uso durante el desarrollo local). También se incluye un controlador de cola null
que descarta trabajos en cola.
Nota Laravel ahora ofrece Horizon, un hermoso panel y sistema de configuración para tus colas alimentadas por Redis. Consulta la documentación completa de Horizon para obtener más información.
Antes de comenzar con las colas de Laravel, es importante entender la distinción entre "conexiones" y "colas". En tu archivo de configuración config/queue.php
, hay una matriz de configuración llamada connections
. Esta opción define las conexiones a servicios de cola como Amazon SQS, Beanstalk o Redis. Sin embargo, cualquier conexión de cola puede tener múltiples "colas", que se pueden pensar como pilas o montones diferentes de trabajos en cola.
Ten en cuenta que cada ejemplo de configuración de conexión en el archivo de configuración queue
contiene un atributo queue
. Esta es la cola predeterminada a la que se enviarán los trabajos cuando se envíen a una conexión específica. En otras palabras, si despachas un trabajo sin definir explícitamente a qué cola debería enviarse, el trabajo se colocará en la cola definida en el atributo queue
de la configuración de la conexión:
use App\Jobs\ProcessPodcast; // Este trabajo se envía a la cola predeterminada de la conexión predeterminada...ProcessPodcast::dispatch(); // Este trabajo se envía a la cola "emails" de la conexión predeterminada...ProcessPodcast::dispatch()->onQueue('emails');
Algunas aplicaciones pueden no necesitar nunca enviar trabajos a múltiples colas, prefiriendo tener una cola simple. Sin embargo, enviar trabajos a múltiples colas puede ser especialmente útil para aplicaciones que desean priorizar o segmentar cómo se procesan los trabajos, ya que el trabajador de cola de Laravel te permite especificar qué colas debería procesar por prioridad. Por ejemplo, si envías trabajos a una cola high
, puedes ejecutar un trabajador que les dé una mayor prioridad de procesamiento:
php artisan queue:work --queue=high,default
Para usar el controlador de cola database
, necesitarás una tabla de base de datos para almacenar los trabajos. Para generar una migración que cree esta tabla, ejecuta el comando Artisan queue:table
. Una vez que se haya creado la migración, puedes migrar tu base de datos usando el comando migrate
:
php artisan queue:table php artisan migrate
Finalmente, no olvides instruir a tu aplicación para que utilice el controlador database
actualizando la variable QUEUE_CONNECTION
en el archivo .env
de tu aplicación:
QUEUE_CONNECTION=database
Para utilizar el controlador de cola redis
, debes configurar una conexión de base de datos Redis en tu archivo de configuración config/database.php
.
Advertencia Las opciones
serializer
ycompression
de Redis no son compatibles con el controlador de colaredis
.
Redis Cluster
Si tu conexión de cola Redis utiliza un clúster de Redis, los nombres de tus colas deben contener una etiqueta de hash clave. Esto es necesario para asegurar que todas las claves de Redis para una cola dada se coloquen en el mismo espacio de hash:
'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => '{default}', 'retry_after' => 90,],
Bloqueo
Cuando se utiliza la cola de Redis, puedes utilizar la opción de configuración block_for
para especificar cuánto tiempo el controlador debe esperar a que un trabajo esté disponible antes de iterar a través del bucle del trabajador y volver a sondear la base de datos de Redis.
Ajustar este valor en función de la carga de tu cola puede ser más eficiente que sondear continuamente la base de datos de Redis en busca de nuevos trabajos. Por ejemplo, puedes establecer el valor en 5
para indicar que el controlador debe bloquearse durante cinco segundos mientras espera a que esté disponible un trabajo:
'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => 'default', 'retry_after' => 90, 'block_for' => 5,],
Advertencia Establecer
block_for
en0
hará que los trabajadores de la cola bloqueen indefinidamente hasta que haya un trabajo disponible. Esto también evitará que se manejen señales comoSIGTERM
hasta que se haya procesado el próximo trabajo.
Las siguientes dependencias son necesarias para los controladores de cola mencionados. Estas dependencias se pueden instalar a través del administrador de paquetes Composer:
aws/aws-sdk-php ~3.0
pda/pheanstalk ~4.0
predis/predis ~1.0
o la extensión PHP phpredisDe forma predeterminada, todos los trabajos en cola para tu aplicación se almacenan en el directorio app/Jobs
. Si el directorio app/Jobs
no existe, se creará cuando ejecutes el comando Artisan make:job
:
php artisan make:job ProcessPodcast
La clase generada implementará la interfaz Illuminate\Contracts\Queue\ShouldQueue
, indicando a Laravel que el trabajo debe ser colocado en la cola para ejecutarse de forma asíncrona.
Nota Las plantillas de trabajos se pueden personalizar mediante la publicación de plantillas.
Las clases de trabajo son muy simples, normalmente contienen solo un método handle
que se invoca cuando el trabajo es procesado por la cola. Para comenzar, veamos un ejemplo de clase de trabajo. En este ejemplo, fingiremos que gestionamos un servicio de publicación de podcasts y necesitamos procesar los archivos de podcast cargados antes de que se publiquen:
<?php namespace App\Jobs; use App\Models\Podcast;use App\Services\AudioProcessor;use Illuminate\Bus\Queueable;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Bus\Dispatchable;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Queue\SerializesModels; class ProcessPodcast implements ShouldQueue{ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** * Crear una nueva instancia de trabajo. */ public function __construct( public Podcast $podcast, ) {} /** * Ejecutar el trabajo. */ public function handle(AudioProcessor $processor): void { // Procesar el podcast subido... }}
En este ejemplo, ten en cuenta que pudimos pasar un modelo Eloquent directamente al constructor del trabajo en cola. Debido al rasgo SerializesModels
que está utilizando el trabajo, los modelos Eloquent y sus relaciones cargadas se serializarán y deserializarán de manera adecuada cuando se esté procesando el trabajo.
Si tu trabajo en cola acepta un modelo Eloquent en su constructor, solo el identificador del modelo se serializará en la cola. Cuando el trabajo se maneje realmente, el sistema de colas recuperará automáticamente la instancia completa del modelo y sus relaciones cargadas desde la base de datos. Este enfoque de serialización de modelos permite que se envíen a tu controlador de colas cargas de trabajo mucho más pequeñas.
handle
El método handle
se invoca cuando el trabajo es procesado por la cola. Ten en cuenta que podemos indicar las dependencias en el método handle
del trabajo. El contenedor de servicios de Laravel inyecta automáticamente estas dependencias.
Si deseas tener un control total sobre cómo el contenedor inyecta dependencias en el método handle
, puedes usar el método bindMethod
del contenedor. El método bindMethod
acepta un callback que recibe el trabajo y el contenedor. Dentro del callback, eres libre de invocar el método handle
como desees. Típicamente, deberías llamar a este método desde el método boot
de tu proveedor de servicios App\Providers\AppServiceProvider
:
use App\Jobs\ProcessPodcast;use App\Services\AudioProcessor;use Illuminate\Contracts\Foundation\Application; $this->app->bindMethod([ProcessPodcast::class, 'handle'], function (ProcessPodcast $job, Application $app) { return $job->handle($app->make(AudioProcessor::class));});
Advertencia Los datos binarios, como el contenido de la imagen en bruto, deben pasar por la función
base64_encode
antes de pasarlos a un trabajo en cola. De lo contrario, es posible que el trabajo no se serialice correctamente a JSON cuando se coloca en la cola.
Dado que todas las relaciones cargadas de modelos Eloquent también se serializan cuando se encola un trabajo, la cadena de trabajo serializada a veces puede volverse bastante grande. Además, cuando se deserializa un trabajo y se vuelven a recuperar las relaciones del modelo desde la base de datos, se recuperarán en su totalidad. Cualquier restricción de relación anterior que se aplicó antes de que se serializara el modelo durante el proceso de encolado del trabajo no se aplicará cuando se deserialice el trabajo. Por lo tanto, si deseas trabajar con un subconjunto de una relación dada, debes restringir esa relación nuevamente dentro de tu trabajo en cola encolado.
O, para evitar que se serialicen las relaciones, puedes llamar al método withoutRelations
en el modelo al establecer el valor de una propiedad. Este método devolverá una instancia del modelo sin sus relaciones cargadas:
/** * Crear una nueva instancia de trabajo. */public function __construct(Podcast $podcast){ $this->podcast = $podcast->withoutRelations();}
Si estás utilizando la promoción de propiedades del constructor de PHP y deseas indicar que un modelo Eloquent no debe tener sus relaciones serializadas, puedes usar el atributo WithoutRelations
:
use Illuminate\Queue\Attributes\WithoutRelations; /** * Crear una nueva instancia de trabajo. */public function __construct( #[WithoutRelations] public Podcast $podcast) {}
Si un trabajo recibe una colección o un array de modelos Eloquent en lugar de un solo modelo, los modelos dentro de esa colección no tendrán sus relaciones restauradas cuando se deserialice y ejecute el trabajo. Esto se hace para evitar un uso excesivo de recursos en trabajos que manejan grandes cantidades de modelos.
Advertencia Los trabajos únicos requieren un controlador de caché que admita bloqueos. Actualmente, los controladores de caché
memcached
,redis
,dynamodb
,database
,file
yarray
admiten bloqueos atómicos. Además, las restricciones de trabajos únicos no se aplican a trabajos dentro de lotes.
A veces, es posible que desees asegurarte de que solo haya una instancia de un trabajo específico en la cola en cualquier momento. Puedes hacerlo implementando la interfaz ShouldBeUnique
en la clase de tu trabajo. Esta interfaz no requiere que definas métodos adicionales en tu clase:
<?php use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Contracts\Queue\ShouldBeUnique; class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique{ ...}
En el ejemplo anterior, el trabajo UpdateSearchIndex
es único. Entonces, el trabajo no se despachará si otra instancia del trabajo ya está en la cola y no ha terminado de procesarse.
En ciertos casos, es posible que desees definir una "clave" específica que haga que el trabajo sea único o que desees especificar un tiempo de espera después del cual el trabajo ya no permanece único. Para lograr esto, puedes definir las propiedades o métodos uniqueId
y uniqueFor
en tu clase de trabajo:
<?php use App\Models\Product;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Contracts\Queue\ShouldBeUnique; class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique{ /** * La instancia del producto. * * @var \App\Product */ public $product; /** * El número de segundos después del cual se liberará el bloqueo único del trabajo. * * @var int */ public $uniqueFor = 3600; /** * Obtener el ID único del trabajo. */ public function uniqueId(): string { return $this->product->id; }}
En el ejemplo anterior, el trabajo UpdateSearchIndex
es único por un ID de producto. Entonces, cualquier nueva tarea del trabajo con el mismo ID de producto se ignorará hasta que el trabajo existente haya completado el procesamiento. Además, si el trabajo existente no se procesa dentro de una hora, el bloqueo único se liberará y se podrá despachar otro trabajo con la misma clave única a la cola.
Advertencia Si tu aplicación despacha trabajos desde varios servidores web o contenedores, debes asegurarte de que todos tus servidores estén comunicándose con el mismo servidor de caché central para que Laravel pueda determinar con precisión si un trabajo es único.
De forma predeterminada, los trabajos únicos se "desbloquean" después de que un trabajo completa su procesamiento o falla todos sus intentos de reintento. Sin embargo, puede haber situaciones en las que desees que tu trabajo se desbloquee inmediatamente antes de que se procese. Para lograr esto, tu trabajo debería implementar el contrato ShouldBeUniqueUntilProcessing
en lugar del contrato ShouldBeUnique
:
<?php use App\Models\Product;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing; class UpdateSearchIndex implements ShouldQueue, ShouldBeUniqueUntilProcessing{ // ...}
Entre bastidores, cuando se despacha un trabajo ShouldBeUnique
, Laravel intenta adquirir un bloqueo con la clave uniqueId
. Si el bloqueo no se adquiere, el trabajo no se despacha. Este bloqueo se libera cuando el trabajo completa su procesamiento o falla todos sus intentos de reintento. Por defecto, Laravel utilizará el controlador de caché predeterminado para obtener este bloqueo. Sin embargo, si deseas utilizar otro controlador para adquirir el bloqueo, puedes definir un método uniqueVia
que devuelva el controlador de caché que se debe usar:
use Illuminate\Contracts\Cache\Repository;use Illuminate\Support\Facades\Cache; class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique{ ... /** * Obtener el controlador de almacenamiento en caché para el bloqueo único del trabajo. */ public function uniqueVia(): Repository { return Cache::driver('redis'); }}
Nota Si solo necesitas limitar el procesamiento concurrente de un trabajo, utiliza el middleware de trabajo
WithoutOverlapping
.
Laravel te permite asegurar la privacidad e integridad de los datos de un trabajo a través de encriptación. Para empezar, simplemente agrega la interfaz ShouldBeEncrypted
a la clase del trabajo. Una vez que se ha agregado esta interfaz a la clase, Laravel cifrará automáticamente tu trabajo antes de colocarlo en una cola:
<?php use Illuminate\Contracts\Queue\ShouldBeEncrypted;use Illuminate\Contracts\Queue\ShouldQueue; class UpdateSearchIndex implements ShouldQueue, ShouldBeEncrypted{ // ...}
Los middleware de trabajo te permiten envolver lógica personalizada alrededor de la ejecución de trabajos en cola, reduciendo el código repetitivo en los propios trabajos. Por ejemplo, considera el siguiente método handle
que aprovecha las funciones de límite de velocidad de Redis de Laravel para permitir que solo se procese un trabajo cada cinco segundos:
use Illuminate\Support\Facades\Redis; /** * Ejecutar el trabajo. */public function handle(): void{ Redis::throttle('key')->block(0)->allow(1)->every(5)->then(function () { info('Lock obtained...'); // Manejar trabajo... }, function () { // No se pudo obtener el bloqueo... return $this->release(5); });}
Aunque este código es válido, la implementación del método handle
se vuelve ruidosa ya que está abarrotada con la lógica de limitación de velocidad de Redis. Además, esta lógica de limitación de velocidad debe duplicarse para cualquier otro trabajo que queramos limitar.
En lugar de limitar la velocidad en el método handle
, podríamos definir un middleware de trabajo que maneje la limitación de velocidad. Laravel no tiene una ubicación predeterminada para los middleware de trabajo, así que puedes colocar los middleware de trabajo en cualquier lugar de tu aplicación. En este ejemplo, los ubicaremos en un directorio app/Jobs/Middleware
:
<?php namespace App\Jobs\Middleware; use Closure;use Illuminate\Support\Facades\Redis; class RateLimited{ /** * Procesar el trabajo en cola. * * @param \Closure(object): void $next */ public function handle(object $job, Closure $next): void { Redis::throttle('key') ->block(0)->allow(1)->every(5) ->then(function () use ($job, $next) { // Bloqueo obtenido... $next($job); }, function () use ($job) { // No se pudo obtener el bloqueo... $job->release(5); }); }}
Como puedes ver, al igual que los middleware de ruta, los middleware de trabajo reciben el trabajo que se está procesando y un callback que debe invocarse para continuar procesando el trabajo.
Después de crear middleware de trabajo, se pueden adjuntar a un trabajo devolviéndolos desde el método middleware
del trabajo. Este método no existe en trabajos generados por el comando make:job
de Artisan, por lo que deberás agregarlo manualmente a la clase de tu trabajo:
use App\Jobs\Middleware\RateLimited; /** * Obtener la middleware por la que debe pasar el trabajo. * * @return array<int, object> */public function middleware(): array{ return [new RateLimited];}
Nota El middleware de trabajo también se puede asignar a escuchadores de eventos en cola, correos electrónicos y notificaciones en cola.
Aunque acabamos de demostrar cómo escribir tu propio middleware de trabajo para limitar la velocidad, Laravel incluye realmente un middleware de limitación de velocidad que puedes utilizar para limitar la velocidad de los trabajos. Al igual que los limitadores de velocidad de ruta, los limitadores de velocidad de trabajo se definen utilizando el método for
de la fachada RateLimiter
.
Por ejemplo, es posible que desees permitir a los usuarios hacer una copia de seguridad de sus datos una vez por hora sin imponer tal límite a los clientes premium. Para lograr esto, puedes definir un RateLimiter
en el método boot
de tu AppServiceProvider
:
use Illuminate\Cache\RateLimiting\Limit;use Illuminate\Support\Facades\RateLimiter; /** * Iniciar cualquier servicio de aplicación. */public function boot(): void{ RateLimiter::for('backups', function (object $job) { return $job->user->vipCustomer() ? Limit::none() : Limit::perHour(1)->by($job->user->id); });}
En el ejemplo anterior, definimos un límite de velocidad por hora; sin embargo, puedes definir fácilmente un límite de velocidad basado en minutos utilizando el método perMinute
. Además, puedes pasar cualquier valor que desees al método by
del límite de velocidad; sin embargo, este valor se utiliza más a menudo para segmentar los límites de velocidad por cliente:
return Limit::perMinute(50)->by($job->user->id);
Una vez que hayas definido tu límite de velocidad, puedes adjuntar el limitador de velocidad a tu trabajo utilizando el middleware Illuminate\Queue\Middleware\RateLimited
. Cada vez que el trabajo supera el límite de velocidad, este middleware devolverá el trabajo a la cola con un retraso adecuado basado en la duración del límite de velocidad.
use Illuminate\Queue\Middleware\RateLimited; /** * Obtener la middleware por la que debe pasar el trabajo. * * @return array<int, object> */public function middleware(): array{ return [new RateLimited('backups')];}
Liberar un trabajo con límite de velocidad de nuevo en la cola seguirá incrementando el número total de attempts
del trabajo. Puedes ajustar las propiedades tries
y maxExceptions
de tu clase de trabajo en consecuencia. O puedes utilizar el método retryUntil
para definir el tiempo hasta que el trabajo ya no deba intentarse.
Si no quieres que un trabajo sea reintentado cuando tiene un límite de velocidad, puedes usar el método dontRelease
:
/** * Obtener la middleware por la que debe pasar el trabajo. * * @return array<int, object> */public function middleware(): array{ return [(new RateLimited('backups'))->dontRelease()];}
Nota Si estás utilizando Redis, puedes usar el middleware
Illuminate\Queue\Middleware\RateLimitedWithRedis
, que está ajustado para Redis y es más eficiente que el middleware básico de limitación de velocidad.
Laravel incluye un middleware Illuminate\Queue\Middleware\WithoutOverlapping
que te permite evitar superposiciones de trabajos según una clave arbitraria. Esto puede ser útil cuando un trabajo en cola está modificando un recurso que solo debería ser modificado por un trabajo a la vez.
Por ejemplo, imaginemos que tienes un trabajo en cola que actualiza la puntuación crediticia de un usuario y quieres evitar superposiciones de trabajos de actualización de puntuación crediticia para el mismo ID de usuario. Para lograr esto, puedes devolver el middleware WithoutOverlapping
desde el método middleware
de tu trabajo:
use Illuminate\Queue\Middleware\WithoutOverlapping; /** * Obtener la middleware por la que debe pasar el trabajo. * * @return array<int, object> */public function middleware(): array{ return [new WithoutOverlapping($this->user->id)];}
Cualquier trabajo que se superponga del mismo tipo se devolverá a la cola. También puedes especificar la cantidad de segundos que deben transcurrir antes de que se intente nuevamente el trabajo liberado:
/** * Obtener la middleware por la que debe pasar el trabajo. * * @return array<int, object> */public function middleware(): array{ return [(new WithoutOverlapping($this->order->id))->releaseAfter(60)];}
Si deseas eliminar inmediatamente cualquier trabajo que se superponga para que no se reintente, puedes usar el método dontRelease
:
/** * Obtener la middleware por la que debe pasar el trabajo. * * @return array<int, object> */public function middleware(): array{ return [(new WithoutOverlapping($this->order->id))->dontRelease()];}
El middleware WithoutOverlapping
funciona con la función de bloqueo atómico de Laravel. A veces, tu trabajo puede fallar inesperadamente o agotarse de manera que el bloqueo no se libere. Por lo tanto, puedes definir explícitamente un tiempo de vencimiento del bloqueo usando el método expireAfter
. Por ejemplo, el siguiente ejemplo indicará a Laravel que libere el bloqueo WithoutOverlapping
tres minutos después de que el trabajo haya comenzado a procesarse:
/** * Obtener la middleware por la que debe pasar el trabajo. * * @return array<int, object> */public function middleware(): array{ return [(new WithoutOverlapping($this->order->id))->expireAfter(180)];}
Advertencia La middleware
WithoutOverlapping
requiere un controlador de caché que admita bloqueos. Actualmente, los controladores de cachémemcached
,redis
,dynamodb
,database
,file
yarray
admiten bloqueos atómicos.
De forma predeterminada, el middleware WithoutOverlapping
solo evitará que se superpongan trabajos de la misma clase. Entonces, aunque dos clases de trabajos diferentes pueden usar la misma clave de bloqueo, no se evitará que se superpongan. Sin embargo, puedes indicarle a Laravel que aplique la clave entre clases de trabajos utilizando el método shared
:
use Illuminate\Queue\Middleware\WithoutOverlapping; class ProviderIsDown{ // ... public function middleware(): array { return [ (new WithoutOverlapping("status:{$this->provider}"))->shared(), ]; }} class ProviderIsUp{ // ... public function middleware(): array { return [ (new WithoutOverlapping("status:{$this->provider}"))->shared(), ]; }}
Laravel incluye un middleware Illuminate\Queue\Middleware\ThrottlesExceptions
que te permite limitar excepciones. Una vez que el trabajo lanza un número dado de excepciones, todos los intentos posteriores de ejecutar el trabajo se retrasan hasta que transcurra un intervalo de tiempo especificado. Este middleware es particularmente útil para trabajos que interactúan con servicios de terceros que no son estables.
Por ejemplo, imaginemos un trabajo en cola que interactúa con una API de terceros que comienza a lanzar excepciones. Para limitar excepciones, puedes devolver el middleware ThrottlesExceptions
desde el método middleware
de tu trabajo. Por lo general, este middleware debe estar emparejado con un trabajo que implementa intentos basados en el tiempo:
use DateTime;use Illuminate\Queue\Middleware\ThrottlesExceptions; /** * Obtener la middleware por la que debe pasar el trabajo. * * @return array<int, object> */public function middleware(): array{ return [new ThrottlesExceptions(10, 5)];} /** * Determinar el momento en que debería vencer el trabajo. */public function retryUntil(): DateTime{ return now()->addMinutes(5);}
El primer argumento del constructor aceptado por el middleware es el número de excepciones que el trabajo puede lanzar antes de ser limitado, mientras que el segundo argumento del constructor es el número de minutos que deben transcurrir antes de que se intente el trabajo nuevamente una vez que ha sido limitado. En el ejemplo de código anterior, si el trabajo lanza 10 excepciones dentro de 5 minutos, esperaremos 5 minutos antes de intentar el trabajo nuevamente.
Cuando un trabajo lanza una excepción pero aún no se ha alcanzado el umbral de excepciones, el trabajo suele volver a intentarse de inmediato. Sin embargo, puedes especificar la cantidad de minutos que debe retrasarse dicho trabajo llamando al método backoff
al adjuntar el middleware al trabajo:
use Illuminate\Queue\Middleware\ThrottlesExceptions; /** * Obtener la middleware por la que debe pasar el trabajo. * * @return array<int, object> */public function middleware(): array{ return [(new ThrottlesExceptions(10, 5))->backoff(5)];}
Internamente, este middleware utiliza el sistema de caché de Laravel para implementar la limitación de velocidad, y el nombre de clase del trabajo se utiliza como la "clave" de la caché. Puedes anular esta clave llamando al método by
al adjuntar el middleware a tu trabajo. Esto puede ser útil si tienes varios trabajos que interactúan con el mismo servicio de terceros y deseas que compartan un "depósito" de limitación de velocidad común:
use Illuminate\Queue\Middleware\ThrottlesExceptions; /** * Obtener la middleware por la que debe pasar el trabajo. * * @return array<int, object> */public function middleware(): array{ return [(new ThrottlesExceptions(10, 10))->by('key')];}
Nota Si estás utilizando Redis, puedes usar el middleware
Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis
, que está ajustado para Redis y es más eficiente que el middleware básico de limitación de excepciones.
Una vez que hayas escrito la clase de tu trabajo, puedes despacharlo usando el método dispatch
en el propio trabajo. Los argumentos pasados al método dispatch
se pasarán al constructor del trabajo:
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller;use App\Jobs\ProcessPodcast;use App\Models\Podcast;use Illuminate\Http\RedirectResponse;use Illuminate\Http\Request; class PodcastController extends Controller{ /** * Almacenar un nuevo podcast. */ public function store(Request $request): RedirectResponse { $podcast = Podcast::create(/* ... */); // ... ProcessPodcast::dispatch($podcast); return redirect('/podcasts'); }}
Si deseas despachar un trabajo condicionalmente, puedes usar los métodos dispatchIf
y dispatchUnless
:
ProcessPodcast::dispatchIf($accountActive, $podcast); ProcessPodcast::dispatchUnless($accountSuspended, $podcast);
En las nuevas aplicaciones de Laravel, el controlador sync
es el controlador de cola predeterminado. Este controlador ejecuta trabajos de forma síncrona en primer plano de la solicitud actual, lo cual es conveniente durante el desarrollo local. Si deseas comenzar a encolar trabajos para su procesamiento en segundo plano, puedes especificar un controlador de cola diferente dentro del archivo de configuración config/queue.php
de tu aplicación.
Si deseas especificar que un trabajo no debe estar disponible inmediatamente para su procesamiento por un trabajador de cola, puedes usar el método delay
al despachar el trabajo. Por ejemplo, especificamos que un trabajo no estará disponible para su procesamiento hasta 10 minutos después de haber sido despachado:
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller;use App\Jobs\ProcessPodcast;use App\Models\Podcast;use Illuminate\Http\RedirectResponse;use Illuminate\Http\Request; class PodcastController extends Controller{ /** * Almacenar un nuevo podcast. */ public function store(Request $request): RedirectResponse { $podcast = Podcast::create(/* ... */); // ... ProcessPodcast::dispatch($podcast) ->delay(now()->addMinutes(10)); return redirect('/podcasts'); }}
Advertencia El servicio de cola Amazon SQS tiene un tiempo máximo de retraso de 15 minutos.
Alternativamente, el método dispatchAfterResponse
retrasa el despacho de un trabajo hasta después de que la respuesta HTTP se envía al navegador del usuario si tu servidor web utiliza FastCGI. Esto permitirá que el usuario comience a usar la aplicación aunque un trabajo en cola aún se esté ejecutando. Esto generalmente solo debe usarse para trabajos que toman alrededor de un segundo, como enviar un correo electrónico. Dado que se procesan dentro de la solicitud HTTP actual, los trabajos despachados de esta manera no requieren que se ejecute un trabajador de cola para que se procesen:
use App\Jobs\SendNotification; SendNotification::dispatchAfterResponse();
También puedes dispatch
un cierre y encadenar el método afterResponse
en el ayudante dispatch
para ejecutar un cierre después de que se haya enviado la respuesta HTTP al navegador:
use App\Mail\WelcomeMessage;use Illuminate\Support\Facades\Mail; dispatch(function () {})->afterResponse();
Si deseas despachar un trabajo de inmediato (de manera síncrona), puedes usar el método dispatchSync
. Al usar este método, el trabajo no se encolará y se ejecutará de inmediato dentro del proceso actual:
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller;use App\Jobs\ProcessPodcast;use App\Models\Podcast;use Illuminate\Http\RedirectResponse;use Illuminate\Http\Request; class PodcastController extends Controller{ /** * Almacenar un nuevo podcast. */ public function store(Request $request): RedirectResponse { $podcast = Podcast::create(/* ... */); // Crear podcast... ProcessPodcast::dispatchSync($podcast); return redirect('/podcasts'); }}
Si bien es completamente aceptable despachar trabajos dentro de transacciones de bases de datos, debes tener especial cuidado para asegurarte de que tu trabajo realmente pueda ejecutarse con éxito. Al despachar un trabajo dentro de una transacción, es posible que el trabajo sea procesado por un trabajador antes de que la transacción principal se haya confirmado. Cuando esto sucede, cualquier actualización que hayas realizado en modelos o registros de bases de datos durante la transacción de base de datos puede que aún no se refleje en la base de datos. Además, cualquier modelo o registro de bases de datos creado dentro de la transacción puede que no exista en la base de datos.
Afortunadamente, Laravel proporciona varios métodos para resolver este problema. En primer lugar, puedes establecer la opción de conexión after_commit
en la matriz de configuración de la conexión de tu cola:
'redis' => [ 'driver' => 'redis', // ... 'after_commit' => true,],
Cuando la opción after_commit
es true
, puedes despachar trabajos dentro de transacciones de bases de datos; sin embargo, Laravel esperará hasta que las transacciones de bases de datos abiertas se hayan confirmado antes de despachar realmente el trabajo. Por supuesto, si no hay transacciones de bases de datos abiertas actualmente, el trabajo se despachará de inmediato.
Si una transacción se revierte debido a una excepción que ocurre durante la transacción, los trabajos despachados durante esa transacción se descartarán.
Nota Configurar la opción
after_commit
entrue
también hará que los escuchadores de eventos, correos electrónicos, notificaciones y eventos de difusión en cola se envíen después de que se hayan confirmado todas las transacciones abiertas de la base de datos.
Si no configuras la opción de conexión after_commit
en true
, aún puedes indicar que un trabajo específico debe despacharse después de que se hayan confirmado todas las transacciones de bases de datos abiertas. Para lograr esto, puedes encadenar el método afterCommit
en tu operación de despacho:
use App\Jobs\ProcessPodcast; ProcessPodcast::dispatch($podcast)->afterCommit();
Del mismo modo, si la opción de configuración after_commit
se establece en true
, puedes indicar que un trabajo específico debe despacharse de inmediato sin esperar a que se confirmen las transacciones de bases de datos abiertas:
ProcessPodcast::dispatch($podcast)->beforeCommit();
El encadenamiento de trabajos te permite especificar una lista de trabajos en cola que se ejecutarán en secuencia después de que el trabajo principal se haya ejecutado con éxito. Si un trabajo en la secuencia falla, el resto de los trabajos no se ejecutarán. Para ejecutar una cadena de trabajos en cola, puedes usar el método chain
proporcionado por la fachada Bus
. El bus de comandos de Laravel es un componente de nivel inferior sobre el que se construye el despacho de trabajos en cola:
use App\Jobs\OptimizePodcast;use App\Jobs\ProcessPodcast;use App\Jobs\ReleasePodcast;use Illuminate\Support\Facades\Bus; Bus::chain([ new ProcessPodcast, new OptimizePodcast, new ReleasePodcast,])->dispatch();
Además de encadenar instancias de clases de trabajos, también puedes encadenar cierres:
Bus::chain([ new ProcessPodcast, new OptimizePodcast, function () { Podcast::update(/* ... */); },])->dispatch();
Advertencia Eliminar trabajos mediante el método
$this->delete()
dentro del trabajo no evitará que se procesen los trabajos encadenados. La cadena solo dejará de ejecutarse si un trabajo en la cadena falla.
Si deseas especificar la conexión y la cola que se deben utilizar para los trabajos encadenados, puedes usar los métodos onConnection
y onQueue
. Estos métodos especifican la conexión de la cola y el nombre de la cola que se deben utilizar a menos que al trabajo en cola se le asigne explícitamente una conexión / cola diferente:
Bus::chain([ new ProcessPodcast, new OptimizePodcast, new ReleasePodcast,])->onConnection('redis')->onQueue('podcasts')->dispatch();
Al encadenar trabajos, puedes usar el método catch
para especificar un cierre que se debe invocar si un trabajo dentro de la cadena falla. El callback dado recibirá la instancia de Throwable
que causó el fallo del trabajo:
use Illuminate\Support\Facades\Bus;use Throwable; Bus::chain([ new ProcessPodcast, new OptimizePodcast, new ReleasePodcast,])->catch(function (Throwable $e) { // Un trabajo dentro de la cadena ha fallado...})->dispatch();
Advertencia Dado que las devoluciones de llamada de cadena se serializan y se ejecutan en un momento posterior por la cola de Laravel, no debes usar la variable
$this
dentro de las devoluciones de llamada de cadena.
Al enviar trabajos a diferentes colas, puedes "categorizar" tus trabajos en cola e incluso priorizar cuántos trabajadores asignas a varias colas. Ten en cuenta que esto no envía trabajos a diferentes "conexiones" de cola según lo definido en tu archivo de configuración de cola, sino solo a colas específicas dentro de una única conexión. Para especificar la cola, usa el método onQueue
al despachar el trabajo:
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller;use App\Jobs\ProcessPodcast;use App\Models\Podcast;use Illuminate\Http\RedirectResponse;use Illuminate\Http\Request; class PodcastController extends Controller{ /** * Almacenar un nuevo podcast. */ public function store(Request $request): RedirectResponse { $podcast = Podcast::create(/* ... */); // Crear podcast... ProcessPodcast::dispatch($podcast)->onQueue('processing'); return redirect('/podcasts'); }}
Alternativamente, puedes especificar la cola del trabajo llamando al método onQueue
dentro del constructor del trabajo:
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class ProcessPodcast implements ShouldQueue{ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** * Crear una nueva instancia de trabajo. */ public function __construct() { $this->onQueue('processing'); }}
Si tu aplicación interactúa con múltiples conexiones de cola, puedes especificar a qué conexión enviar un trabajo usando el método onConnection
:
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller;use App\Jobs\ProcessPodcast;use App\Models\Podcast;use Illuminate\Http\RedirectResponse;use Illuminate\Http\Request; class PodcastController extends Controller{ /** * Almacenar un nuevo podcast. */ public function store(Request $request): RedirectResponse { $podcast = Podcast::create(/* ... */); // Crear podcast... ProcessPodcast::dispatch($podcast)->onConnection('sqs'); return redirect('/podcasts'); }}
Puedes encadenar los métodos onConnection
y onQueue
para especificar la conexión y la cola para un trabajo:
ProcessPodcast::dispatch($podcast) ->onConnection('sqs') ->onQueue('processing');
Alternativamente, puedes especificar la conexión del trabajo llamando al método onConnection
dentro del constructor del trabajo:
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class ProcessPodcast implements ShouldQueue{ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** * Crear una nueva instancia de trabajo. */ public function __construct() { $this->onConnection('sqs'); }}
Si uno de tus trabajos en cola encuentra un error, es probable que no desees que siga reintentándose indefinidamente. Por lo tanto, Laravel proporciona diversas formas de especificar cuántas veces o durante cuánto tiempo se puede intentar un trabajo.
Un enfoque para especificar el número máximo de intentos que se pueden realizar en un trabajo es mediante el interruptor --tries
en la línea de comandos de Artisan. Esto se aplicará a todos los trabajos procesados por el trabajador, a menos que el trabajo en proceso especifique el número de intentos que se pueden realizar:
php artisan queue:work --tries=3
Si un trabajo supera su número máximo de intentos, se considerará un trabajo "fallido". Para obtener más información sobre el manejo de trabajos fallidos, consulta la documentación de trabajos fallidos. Si se proporciona --tries=0
al comando queue:work
, el trabajo se volverá a intentar indefinidamente.
Puedes adoptar un enfoque más detallado al definir el número máximo de intentos que se pueden realizar en la propia clase del trabajo. Si se especifica el número máximo de intentos en el trabajo, tendrá prioridad sobre el valor --tries
proporcionado en la línea de comandos:
<?php namespace App\Jobs; class ProcessPodcast implements ShouldQueue{ /** * El número de intentos que se pueden realizar para ejecutar el trabajo. * * @var int */ public $tries = 5;}
Como alternativa a definir cuántas veces se puede intentar un trabajo antes de que falle, puedes definir una hora en la que el trabajo ya no debe intentarse. Esto permite que un trabajo se intente cualquier número de veces dentro de un marco de tiempo dado. Para definir la hora en la que un trabajo ya no debe intentarse, agrega un método retryUntil
a tu clase de trabajo. Este método debe devolver una instancia de DateTime
:
use DateTime; /** * Determinar el momento en que debería vencer el trabajo. */public function retryUntil(): DateTime{ return now()->addMinutes(10);}
Nota También puedes definir una propiedad
tries
o el métodoretryUntil
en tus escuchadores de eventos en cola.
A veces, es posible que desees especificar que un trabajo se pueda intentar muchas veces, pero debería fallar si los reintentos son provocados por un número determinado de excepciones no manejadas (en lugar de ser liberado por el método release
directamente). Para lograr esto, puedes definir una propiedad maxExceptions
en tu clase de trabajo:
<?php namespace App\Jobs; use Illuminate\Support\Facades\Redis; class ProcessPodcast implements ShouldQueue{ /** * El número de intentos que se pueden realizar para ejecutar el trabajo. * * @var int */ public $tries = 25; /** * El número máximo de excepciones no gestionadas permitidas antes de fallar. * * @var int */ public $maxExceptions = 3; /** * Ejecutar el trabajo. */ public function handle(): void { Redis::throttle('key')->allow(10)->every(60)->then(function () { // Bloqueo obtenido, procesar el podcast... }, function () { // No se pudo obtener el bloqueo... return $this->release(10); }); }}
En este ejemplo, el trabajo se libera durante diez segundos si la aplicación no puede obtener un bloqueo de Redis y seguirá siendo reintentado hasta 25 veces. Sin embargo, el trabajo fallará si se lanzan tres excepciones no manejadas por el trabajo.
A menudo, sabes aproximadamente cuánto tiempo esperas que duren tus trabajos en cola. Por esta razón, Laravel te permite especificar un valor de "timeout". Por defecto, el valor de timeout es de 60 segundos. Si un trabajo está en proceso durante más segundos de los especificados por el valor de timeout, el trabajador que procesa el trabajo saldrá con un error. Normalmente, el trabajador se reiniciará automáticamente por un gestor de procesos configurado en tu servidor.
El número máximo de segundos que pueden durar los trabajos se puede especificar utilizando el interruptor --timeout
en la línea de comandos de Artisan:
php artisan queue:work --timeout=30
Si el trabajo supera su número máximo de intentos al agotar continuamente el tiempo, se marcará como fallido.
También puedes definir el número máximo de segundos que se permite que un trabajo se ejecute en la propia clase del trabajo. Si se especifica el tiempo de espera en el trabajo, tendrá prioridad sobre cualquier tiempo de espera especificado en la línea de comandos:
<?php namespace App\Jobs; class ProcessPodcast implements ShouldQueue{ /** * El número de segundos que puede ejecutarse el trabajo antes de vencer. * * @var int */ public $timeout = 120;}
A veces, los procesos de bloqueo de E/S, como sockets o conexiones HTTP salientes, pueden no respetar el tiempo de espera especificado. Por lo tanto, al usar estas funciones, siempre debes intentar especificar un tiempo de espera utilizando sus API. Por ejemplo, al usar Guzzle, siempre debes especificar un valor de tiempo de espera para la conexión y la solicitud.
Advertencia La extensión de PHP
pcntl
debe estar instalada para especificar tiempos de espera de trabajos. Además, el valor de "timeout" de un trabajo siempre debe ser inferior a su valor de "retry after". De lo contrario, el trabajo puede volver a intentarse antes de que realmente haya terminado de ejecutarse o haya agotado el tiempo de espera.
Si deseas indicar que un trabajo debe marcarse como fallido al agotar el tiempo de espera, puedes definir la propiedad $failOnTimeout
en la clase del trabajo:
/** * Indicar si el trabajo debería marcarse como fallido al vencer. * * @var bool */public $failOnTimeout = true;
Si se lanza una excepción mientras se procesa el trabajo, el trabajo se liberará automáticamente de nuevo a la cola para que se pueda intentar nuevamente. El trabajo seguirá siendo liberado hasta que se haya intentado el número máximo de veces permitido por tu aplicación. El número máximo de intentos se define con el interruptor --tries
utilizado en el comando Artisan queue:work
. Alternativamente, el número máximo de intentos se puede definir en la propia clase del trabajo. Más información sobre cómo ejecutar el trabajador de la cola se encuentra a continuación.
A veces, es posible que desees liberar manualmente un trabajo de nuevo a la cola para que se pueda intentar nuevamente en otro momento. Puedes hacer esto llamando al método release
:
/** * Ejecutar el trabajo. */public function handle(): void{ // ... $this->release();}
Por defecto, el método release
liberará el trabajo de nuevo a la cola para su procesamiento inmediato. Sin embargo, puedes indicar a la cola que no ponga el trabajo disponible para su procesamiento hasta que haya transcurrido un número dado de segundos pasando un entero o una instancia de fecha al método release
:
$this->release(10); $this->release(now()->addSeconds(10));
Ocasionalmente, es posible que necesites marcar manualmente un trabajo como "fallido". Para hacerlo, puedes llamar al método fail
:
/** * Ejecutar el trabajo. */public function handle(): void{ // ... $this->fail();}
Si deseas marcar tu trabajo como fallido debido a una excepción que has capturado, puedes pasar la excepción al método fail
. O, por conveniencia, puedes pasar un mensaje de error de cadena que se convertirá en una excepción por ti:
$this->fail($exception); $this->fail('Something went wrong.');
Nota Para obtener más información sobre trabajos fallidos, consulta la documentación sobre el manejo de trabajos fallidos.
La función de agrupación de trabajos de Laravel te permite ejecutar fácilmente un lote de trabajos y luego realizar alguna acción cuando el lote de trabajos haya completado su ejecución. Antes de comenzar, debes crear una migración de base de datos para construir una tabla que contenga información meta sobre tus lotes de trabajos, como su porcentaje de completado. Esta migración se puede generar usando el comando Artisan queue:batches-table
:
php artisan queue:batches-table php artisan migrate
Para definir un trabajo que se pueda agrupar, debes crear un trabajo en cola como de costumbre; sin embargo, debes agregar el rasgo Illuminate\Bus\Batchable
a la clase del trabajo. Este rasgo proporciona acceso a un método batch
que se puede utilizar para recuperar el lote actual en el que se está ejecutando el trabajo:
<?php namespace App\Jobs; use Illuminate\Bus\Batchable;use Illuminate\Bus\Queueable;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Bus\Dispatchable;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Queue\SerializesModels; class ImportCsv implements ShouldQueue{ use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** * Ejecutar el trabajo. */ public function handle(): void { if ($this->batch()->cancelled()) { // Determinar si el lote ha sido cancelado... return; } // Importar una porción del archivo CSV... }}
Para despachar un lote de trabajos, debes utilizar el método batch
de la fachada Bus
. Por supuesto, la agrupación es principalmente útil cuando se combina con devoluciones de llamada de finalización. Entonces, puedes usar los métodos then
, catch
y finally
para definir devoluciones de llamada de finalización para el lote. Cada una de estas devoluciones de llamada recibirá una instancia de Illuminate\Bus\Batch
cuando se invoquen. En este ejemplo, imaginemos que estamos encolando un lote de trabajos que procesa un número dado de filas de un archivo CSV:
use App\Jobs\ImportCsv;use Illuminate\Bus\Batch;use Illuminate\Support\Facades\Bus;use Throwable; $batch = Bus::batch([ new ImportCsv(1, 100), new ImportCsv(101, 200), new ImportCsv(201, 300), new ImportCsv(301, 400), new ImportCsv(401, 500),])->then(function (Batch $batch) { // Todos los trabajos se completaron con éxito...})->catch(function (Batch $batch, Throwable $e) { // Se detectó el primer fallo del trabajo del lote...})->finally(function (Batch $batch) { // El lote ha terminado de ejecutarse...})->dispatch(); return $batch->id;
La ID del lote, que se puede acceder mediante la propiedad $batch->id
, se puede utilizar para consultar el bus de comandos de Laravel para obtener información sobre el lote después de haber sido despachado.
Advertencia Dado que las devoluciones de llamada de lotes se serializan y se ejecutan en un momento posterior por la cola de Laravel, no debes usar la variable
$this
dentro de las devoluciones de llamada.
Algunas herramientas como Laravel Horizon y Laravel Telescope pueden proporcionar información de depuración más fácil de usar para lotes si los lotes tienen nombres. Para asignar un nombre arbitrario a un lote, puedes llamar al método name
mientras defines el lote:
$batch = Bus::batch([ // ...])->then(function (Batch $batch) { // Todos los trabajos se completaron con éxito...})->name('Import CSV')->dispatch();
Si deseas especificar la conexión y la cola que se deben usar para los trabajos en lote, puedes usar los métodos onConnection
y onQueue
. Todos los trabajos en lote deben ejecutarse dentro de la misma conexión y cola:
$batch = Bus::batch([ // ...])->then(function (Batch $batch) { // Todos los trabajos se completaron con éxito...})->onConnection('redis')->onQueue('imports')->dispatch();
Puedes definir un conjunto de trabajos encadenados dentro de un lote colocando los trabajos encadenados dentro de un array. Por ejemplo, podemos ejecutar dos cadenas de trabajos en paralelo y ejecutar una devolución de llamada cuando ambas cadenas de trabajos hayan terminado de procesar:
use App\Jobs\ReleasePodcast;use App\Jobs\SendPodcastReleaseNotification;use Illuminate\Bus\Batch;use Illuminate\Support\Facades\Bus; Bus::batch([ [ new ReleasePodcast(1), new SendPodcastReleaseNotification(1), ], [ new ReleasePodcast(2), new SendPodcastReleaseNotification(2), ],])->then(function (Batch $batch) { // ...})->dispatch();
Inversamente, puedes ejecutar lotes de trabajos dentro de una cadena definiendo lotes dentro de la cadena. Por ejemplo, podrías ejecutar primero un lote de trabajos para liberar varios podcasts y luego un lote de trabajos para enviar las notificaciones de lanzamiento:
use App\Jobs\FlushPodcastCache;use App\Jobs\ReleasePodcast;use App\Jobs\SendPodcastReleaseNotification;use Illuminate\Support\Facades\Bus; Bus::chain([ new FlushPodcastCache, Bus::batch([ new ReleasePodcast(1), new ReleasePodcast(2), ]), Bus::batch([ new SendPodcastReleaseNotification(1), new SendPodcastReleaseNotification(2), ]),])->dispatch();
A veces puede ser útil agregar trabajos adicionales a un lote desde dentro de un trabajo agrupado. Este patrón puede ser útil cuando necesitas agrupar miles de trabajos que pueden tardar demasiado en despacharse durante una solicitud web. Así que, en su lugar, es posible que desees despachar un lote inicial de trabajos "cargadores" que alimenten el lote con aún más trabajos:
$batch = Bus::batch([ new LoadImportBatch, new LoadImportBatch, new LoadImportBatch,])->then(function (Batch $batch) { // Todos los trabajos se completaron con éxito...})->name('Import Contacts')->dispatch();
En este ejemplo, usaremos el trabajo LoadImportBatch
para alimentar el lote con trabajos adicionales. Para lograr esto, podemos usar el método add
en la instancia del lote que se puede acceder mediante el método batch
del trabajo:
use App\Jobs\ImportContacts;use Illuminate\Support\Collection; /** * Ejecutar el trabajo. */public function handle(): void{ if ($this->batch()->cancelled()) { return; } $this->batch()->add(Collection::times(1000, function () { return new ImportContacts; }));}
Advertencia Solo puedes agregar trabajos a un lote desde dentro de un trabajo que pertenezca al mismo lote.
La instancia Illuminate\Bus\Batch
que se proporciona a las devoluciones de llamada de finalización del lote tiene una variedad de propiedades y métodos para ayudarte a interactuar e inspeccionar un lote dado de trabajos:
// El UUID del lote...$batch->id; // El nombre del lote (si aplica)...$batch->name; // El número de trabajos asignados al lote...$batch->totalJobs; // El número de trabajos que no han sido procesados por la cola...$batch->pendingJobs; // El número de trabajos que han fallado...$batch->failedJobs; // El número de trabajos que se han procesado hasta ahora...$batch->processedJobs(); // El porcentaje de finalización del lote (0-100)...$batch->progress(); // Indica si el lote ha terminado de ejecutarse...$batch->finished(); // Cancelar la ejecución del lote...$batch->cancel(); // Indica si el lote ha sido cancelado...$batch->cancelled();
Todas las instancias de Illuminate\Bus\Batch
son serializables en JSON, lo que significa que puedes devolverlas directamente desde una de las rutas de tu aplicación para obtener un paquete JSON que contenga información sobre el lote, incluido su progreso de finalización. Esto facilita mostrar información sobre el progreso de finalización del lote en la interfaz de usuario de tu aplicación.
Para recuperar un lote por su ID, puedes utilizar el método findBatch
de la fachada Bus
:
use Illuminate\Support\Facades\Bus;use Illuminate\Support\Facades\Route; Route::get('/batch/{batchId}', function (string $batchId) { return Bus::findBatch($batchId);});
A veces puede ser necesario cancelar la ejecución de un lote dado. Esto se puede lograr llamando al método cancel
en la instancia de Illuminate\Bus\Batch
:
/** * Ejecutar el trabajo. */public function handle(): void{ if ($this->user->exceedsImportLimit()) { return $this->batch()->cancel(); } if ($this->batch()->cancelled()) { return; }}
Como habrás notado en los ejemplos anteriores, los trabajos agrupados deben determinar típicamente si su lote correspondiente ha sido cancelado antes de continuar con la ejecución. Sin embargo, por conveniencia, puedes asignar el middleware SkipIfBatchCancelled
al trabajo en su lugar. Como su nombre indica, este middleware indicará a Laravel que no procese el trabajo si su lote correspondiente ha sido cancelado:
use Illuminate\Queue\Middleware\SkipIfBatchCancelled; /** * Obtener la middleware por la que debe pasar el trabajo. */public function middleware(): array{ return [new SkipIfBatchCancelled];}
Cuando un trabajo agrupado falla, se invocará la devolución de llamada catch
(si está asignada). Esta devolución de llamada solo se invoca para el primer trabajo que falla dentro del lote.
Cuando un trabajo dentro de un lote falla, Laravel marcará automáticamente el lote como "cancelado". Si lo deseas, puedes desactivar este comportamiento para que un fallo de trabajo no marque automáticamente el lote como cancelado. Esto se puede lograr llamando al método allowFailures
al despachar el lote:
$batch = Bus::batch([ // ...])->then(function (Batch $batch) { // Todos los trabajos se completaron con éxito...})->allowFailures()->dispatch();
Por conveniencia, Laravel proporciona un comando Artisan queue:retry-batch
que te permite volver a intentar fácilmente todos los trabajos fallidos de un lote dado. El comando queue:retry-batch
acepta el UUID del lote cuyos trabajos fallidos deben volver a intentarse:
php artisan queue:retry-batch 32dbc76c-4f82-4749-b610-a639fe0099b5
Sin la poda, la tabla job_batches
puede acumular registros muy rápidamente. Para mitigar esto, debes programar el comando Artisan queue:prune-batches
para que se ejecute diariamente:
$schedule->command('queue:prune-batches')->daily();
De forma predeterminada, todos los lotes finalizados que tienen más de 24 horas se eliminarán. Puedes usar la opción hours
al llamar al comando para determinar cuánto tiempo retener los datos del lote. Por ejemplo, el siguiente comando eliminará todos los lotes que finalizaron hace más de 48 horas:
$schedule->command('queue:prune-batches --hours=48')->daily();
A veces, tu tabla jobs_batches
puede acumular registros de lotes para lotes que nunca se completaron correctamente, como lotes donde un trabajo falló y ese trabajo nunca se volvió a intentar correctamente. Puedes indicar al comando queue:prune-batches
que poden estos registros de lotes no terminados usando la opción unfinished
:
$schedule->command('queue:prune-batches --hours=48 --unfinished=72')->daily();
De manera similar, tu tabla jobs_batches
también puede acumular registros de lotes para lotes cancelados. Puedes indicar al comando queue:prune-batches
que poden estos registros de lotes cancelados usando la opción cancelled
:
$schedule->command('queue:prune-batches --hours=48 --cancelled=72')->daily();
En lugar de despachar una clase de trabajo a la cola, también puedes despachar un cierre. Esto es ideal para tareas rápidas y sencillas que deben ejecutarse fuera del ciclo de solicitud actual. Al despachar cierres a la cola, el contenido del código del cierre se firma criptográficamente para que no se pueda modificar en tránsito:
$podcast = App\Podcast::find(1); dispatch(function () use ($podcast) { $podcast->publish();});
Usando el método catch
, puedes proporcionar un cierre que se ejecutará si el cierre en cola no se completa correctamente después de agotar todos los intentos de reintento configurados en tu cola:
use Throwable; dispatch(function () use ($podcast) { $podcast->publish();})->catch(function (Throwable $e) { // Este trabajo ha fallado...});
Advertencia Dado que las devoluciones de llamada
catch
se serializan y se ejecutan en un momento posterior por la cola de Laravel, no debes usar la variable$this
dentro de las devoluciones de llamadacatch
.
queue:work
Laravel incluye un comando Artisan que iniciará un trabajador de la cola y procesará nuevos trabajos a medida que se empujan a la cola. Puedes ejecutar el trabajador utilizando el comando Artisan queue:work
. Ten en cuenta que una vez que se haya iniciado el comando queue:work
, continuará ejecutándose hasta que se detenga manualmente o cierres tu terminal:
php artisan queue:work
Nota Para mantener el proceso
queue:work
en ejecución de forma permanente en segundo plano, debes utilizar un monitor de procesos como Supervisor para asegurarte de que el trabajador de la cola no se detenga.
Puedes incluir la bandera -v
al invocar el comando queue:work
si deseas que se incluyan las ID de trabajo procesadas en la salida del comando:
php artisan queue:work -v
Recuerda que los trabajadores de la cola son procesos de larga duración y almacenan el estado de la aplicación cargada en memoria. Como resultado, no notarán los cambios en tu base de código después de haber sido iniciados. Por lo tanto, durante tu proceso de implementación, asegúrate de reiniciar tus trabajadores de la cola. Además, recuerda que cualquier estado estático creado o modificado por tu aplicación no se restablecerá automáticamente entre trabajos.
Alternativamente, puedes ejecutar el comando queue:listen
. Cuando usas el comando queue:listen
, no tienes que reiniciar manualmente el trabajador cuando deseas recargar tu código actualizado o restablecer el estado de la aplicación; sin embargo, este comando es significativamente menos eficiente que el comando queue:work
:
php artisan queue:listen
Para asignar varios trabajadores a una cola y procesar trabajos de manera concurrente, simplemente debes iniciar varios procesos queue:work
. Esto se puede hacer localmente a través de múltiples pestañas en tu terminal o en producción utilizando la configuración de tu administrador de procesos. Cuando uses Supervisor, puedes utilizar el valor de configuración numprocs
.
También puedes especificar qué conexión de cola debe utilizar el trabajador. El nombre de conexión pasado al comando work
debe corresponder a una de las conexiones definidas en tu archivo de configuración config/queue.php
:
php artisan queue:work redis
De forma predeterminada, el comando queue:work
solo procesa trabajos para la cola predeterminada en una conexión dada. Sin embargo, puedes personalizar aún más tu trabajador de la cola al procesar solo colas específicas para una conexión dada. Por ejemplo, si todos tus correos electrónicos se procesan en una cola emails
en tu conexión de cola redis
, puedes emitir el siguiente comando para iniciar un trabajador que solo procese esa cola:
php artisan queue:work redis --queue=emails
La opción --once
se puede usar para indicar al trabajador que solo procese un trabajo de la cola:
php artisan queue:work --once
La opción --max-jobs
se puede usar para indicar al trabajador que procese el número dado de trabajos y luego salga. Esta opción puede ser útil cuando se combina con Supervisor para que tus trabajadores se reinicien automáticamente después de procesar un número determinado de trabajos, liberando cualquier memoria que puedan haber acumulado:
php artisan queue:work --max-jobs=1000
La opción --stop-when-empty
se puede usar para indicar al trabajador que procese todos los trabajos y luego salga de manera elegante. Esta opción puede ser útil al procesar colas de Laravel dentro de un contenedor Docker si deseas apagar el contenedor después de que la cola esté vacía:
php artisan queue:work --stop-when-empty
La opción --max-time
se puede usar para indicar al trabajador que procese trabajos durante el número dado de segundos y luego salga. Esta opción puede ser útil cuando se combina con Supervisor para que tus trabajadores se reinicien automáticamente después de procesar trabajos durante un tiempo determinado, liberando cualquier memoria que puedan haber acumulado:
# Process jobs for one hour and then exit...php artisan queue:work --max-time=3600
Cuando hay trabajos disponibles en la cola, el trabajador seguirá procesando trabajos sin demora entre trabajos. Sin embargo, la opción sleep
determina cuántos segundos el trabajador "dormirá" si no hay trabajos disponibles. Por supuesto, mientras duerme, el trabajador no procesará nuevos trabajos:
php artisan queue:work --sleep=3
Los trabajadores de la cola de daemon no reinician el marco antes de procesar cada trabajo. Por lo tanto, debes liberar cualquier recurso pesado después de que cada trabajo se complete. Por ejemplo, si estás haciendo manipulación de imágenes con la biblioteca GD, debes liberar la memoria con imagedestroy
cuando hayas terminado de procesar la imagen.
A veces puede ser necesario dar prioridad a cómo se procesan tus colas. Por ejemplo, en tu archivo de configuración config/queue.php
, puedes establecer la cola
predeterminada para tu conexión redis
en low
. Sin embargo, ocasionalmente puedes querer enviar un trabajo a una cola de prioridad high
de la siguiente manera:
dispatch((new Job)->onQueue('high'));
Para iniciar un trabajador que verifique que se procesen todos los trabajos de la cola high
antes de continuar con cualquier trabajo en la cola low
, pasa una lista de nombres de colas separados por comas al comando work
:
php artisan queue:work --queue=high,low
Dado que los trabajadores de la cola son procesos de larga duración, no notarán los cambios en tu código sin ser reiniciados. Entonces, la forma más sencilla de implementar una aplicación que utiliza trabajadores de la cola es reiniciar los trabajadores durante tu proceso de implementación. Puedes reiniciar todos los trabajadores de manera segura emitiendo el comando queue:restart
:
php artisan queue:restart
Este comando instruirá a todos los trabajadores de la cola a salir de manera elegante después de que terminen de procesar su trabajo actual para que no se pierda ningún trabajo existente. Dado que los trabajadores de la cola saldrán cuando se ejecute el comando queue:restart
, deberías estar ejecutando un administrador de procesos como Supervisor para reiniciar automáticamente los trabajadores de la cola.
Nota La cola utiliza el almacén de caché para almacenar señales de reinicio, por lo que debes verificar que un controlador de caché esté configurado correctamente para tu aplicación antes de usar esta función.
En tu archivo de configuración config/queue.php
, cada conexión de cola define una opción retry_after
. Esta opción especifica cuántos segundos debe esperar la conexión de la cola antes de volver a intentar un trabajo que se está procesando. Por ejemplo, si el valor de retry_after
está configurado en 90
, el trabajo se volverá a colocar en la cola si ha estado procesándose durante 90 segundos sin ser liberado o eliminado. Típicamente, deberías establecer el valor de retry_after
en el número máximo de segundos que razonablemente deberían tomar tus trabajos para completar el procesamiento.
Advertencia La única conexión de cola que no contiene un valor
retry_after
es Amazon SQS. SQS volverá a intentar el trabajo según el Tiempo de visibilidad predeterminado, que se gestiona dentro de la consola de AWS.
El comando Artisan queue:work
expone una opción --timeout
. De forma predeterminada, el valor de --timeout
es de 60 segundos. Si un trabajo está procesando durante más tiempo del especificado por el valor de tiempo de espera, el trabajador que procesa el trabajo saldrá con un error. Típicamente, el trabajador se reiniciará automáticamente mediante un administrador de procesos configurado en tu servidor:
php artisan queue:work --timeout=60
La opción de configuración retry_after
y la opción CLI --timeout
son diferentes, pero trabajan juntas para garantizar que los trabajos no se pierdan y que los trabajos solo se procesen con éxito una vez.
Advertencia El valor de
--timeout
siempre debe ser al menos unos segundos más corto que tu valor de configuración deretry_after
. Esto asegurará que un trabajador que procesa un trabajo congelado siempre se termine antes de que se vuelva a intentar el trabajo. Si tu opción--timeout
es más larga que tu valor de configuraciónretry_after
, tus trabajos pueden procesarse dos veces.
En producción, necesitas una forma de mantener en funcionamiento tus procesos queue:work
. Un proceso queue:work
puede dejar de ejecutarse por diversas razones, como un tiempo de espera del trabajador superado o la ejecución del comando queue:restart
.
Por esta razón, necesitas configurar un monitor de procesos que pueda detectar cuando tus procesos queue:work
salen y reiniciarlos automáticamente. Además, los monitores de procesos te permiten especificar cuántos procesos queue:work
te gustaría ejecutar simultáneamente. Supervisor es un monitor de procesos comúnmente utilizado en entornos de Linux y discutiremos cómo configurarlo en la siguiente documentación.
Supervisor es un monitor de procesos para el sistema operativo Linux y reiniciará automáticamente tus procesos queue:work
si fallan. Para instalar Supervisor en Ubuntu, puedes usar el siguiente comando:
sudo apt-get install supervisor
Nota Si configurar y gestionar Supervisor por ti mismo parece abrumador, considera utilizar Laravel Forge, que instalará y configurará automáticamente Supervisor para tus proyectos Laravel de producción.
Los archivos de configuración de Supervisor se almacenan típicamente en el directorio /etc/supervisor/conf.d
. Dentro de este directorio, puedes crear cualquier cantidad de archivos de configuración que indiquen a Supervisor cómo deben monitorearse tus procesos. Por ejemplo, creemos un archivo laravel-worker.conf
que inicie y supervise los procesos queue:work
:
[program:laravel-worker]process_name=%(program_name)s_%(process_num)02dcommand=php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3 --max-time=3600autostart=trueautorestart=truestopasgroup=truekillasgroup=trueuser=forgenumprocs=8redirect_stderr=truestdout_logfile=/home/forge/app.com/worker.logstopwaitsecs=3600
En este ejemplo, la directiva numprocs
instruirá a Supervisor a ejecutar ocho procesos queue:work
y monitorear todos ellos, reiniciándolos automáticamente si fallan. Debes cambiar la directiva command
de la configuración para reflejar tu conexión de cola y las opciones del trabajador deseadas.
Advertencia Debes asegurarte de que el valor de
stopwaitsecs
sea mayor que la cantidad de segundos consumidos por tu trabajo de mayor duración. De lo contrario, Supervisor puede matar el trabajo antes de que termine de procesarse.
Una vez que se haya creado el archivo de configuración, puedes actualizar la configuración de Supervisor y comenzar los procesos usando los siguientes comandos:
sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start "laravel-worker:*"
Para obtener más información sobre Supervisor, consulta la documentación de Supervisor.
A veces, tus trabajos en cola fallarán. ¡No te preocupes, las cosas no siempre salen como se planean! Laravel incluye una forma conveniente de especificar el número máximo de intentos que se debe realizar en un trabajo. Después de que un trabajo asíncrono haya superado este número de intentos, se insertará en la tabla de base de datos failed_jobs
. Los trabajos despachados sincrónicamente que fallan no se almacenan en esta tabla y sus excepciones son manejadas inmediatamente por la aplicación.
Típicamente, una migración para crear la tabla failed_jobs
ya está presente en las nuevas aplicaciones de Laravel. Sin embargo, si tu aplicación no contiene una migración para esta tabla, puedes usar el comando queue:failed-table
para crear la migración:
php artisan queue:failed-table php artisan migrate
Al ejecutar un proceso de trabajador de la cola, puedes especificar el número máximo de veces que se debe intentar un trabajo usando el interruptor --tries
en el comando queue:work
. Si no especificas un valor para la opción --tries
, los trabajos solo se intentarán una vez o tantas veces como se especifique en la propiedad $tries
de la clase de trabajo:
php artisan queue:work redis --tries=3
Usando la opción --backoff
, puedes especificar cuántos segundos Laravel debe esperar antes de volver a intentar un trabajo que ha encontrado una excepción. Por defecto, un trabajo se vuelve a colocar inmediatamente en la cola para que se pueda intentar nuevamente:
php artisan queue:work redis --tries=3 --backoff=3
Si deseas configurar cuántos segundos Laravel debe esperar antes de volver a intentar un trabajo que ha encontrado una excepción de manera específica para cada trabajo, puedes hacerlo definiendo una propiedad backoff
en tu clase de trabajo:
/** * El número de segundos que debe esperar antes de volver a intentar el trabajo. * * @var int */public $backoff = 3;
Si necesitas lógica más compleja para determinar el tiempo de espera posterior del trabajo, puedes definir un método backoff
en tu clase de trabajo:
/*** Calcular el número de segundos que debe esperar antes de volver a intentar el trabajo.*/public function backoff(): int{ return 3;}
Puedes configurar fácilmente reintentos "exponenciales" devolviendo un array de valores de tiempo de espera desde el método backoff
. En este ejemplo, el retraso de reintento será de 1 segundo para el primer reintento, 5 segundos para el segundo reintento, 10 segundos para el tercer reintento y 10 segundos para cada reintento subsiguiente si quedan más intentos:
/*** Calcular el número de segundos que debe esperar antes de volver a intentar el trabajo.** @return array<int, int>*/public function backoff(): array{ return [1, 5, 10];}
Cuando un trabajo en particular falla, es posible que desees enviar una alerta a tus usuarios o revertir cualquier acción que haya sido completada parcialmente por el trabajo. Para lograr esto, puedes definir un método failed
en tu clase de trabajo. La instancia Throwable
que causó que el trabajo fallara se pasará al método failed
:
<?php namespace App\Jobs; use App\Models\Podcast;use App\Services\AudioProcessor;use Illuminate\Bus\Queueable;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Queue\SerializesModels;use Throwable; class ProcessPodcast implements ShouldQueue{ use InteractsWithQueue, Queueable, SerializesModels; /** * Crear una nueva instancia de trabajo. */ public function __construct( public Podcast $podcast, ) {} /** * Ejecutar el trabajo. */ public function handle(AudioProcessor $processor): void { // Procesar el podcast subido... } /** * Manejar el fallo del trabajo. */ public function failed(Throwable $exception): void { // Enviar notificación de fallo al usuario, etc... }}
Advertencia Se instancia una nueva instancia del trabajo antes de invocar el método
failed
; por lo tanto, cualquier modificación de propiedades de clase que haya ocurrido dentro del métodohandle
se perderá.
Para ver todos los trabajos fallidos que se han insertado en tu tabla de base de datos failed_jobs
, puedes usar el comando Artisan queue:failed
:
php artisan queue:failed
El comando queue:failed
mostrará el ID del trabajo, la conexión, la cola, el tiempo de falla y otra información sobre el trabajo. El ID del trabajo se puede usar para volver a intentar el trabajo fallido. Por ejemplo, para volver a intentar un trabajo fallido que tiene un ID de ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece
, emite el siguiente comando:
php artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece
Si es necesario, puedes pasar varios IDs al comando:
php artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece 91401d2c-0784-4f43-824c-34f94a33c24d
También puedes volver a intentar todos los trabajos fallidos para una cola en particular:
php artisan queue:retry --queue=name
Para volver a intentar todos tus trabajos fallidos, ejecuta el comando queue:retry
y pasa all
como ID:
php artisan queue:retry all
Si deseas eliminar un trabajo fallido, puedes usar el comando queue:forget
:
php artisan queue:forget 91401d2c-0784-4f43-824c-34f94a33c24d
Nota Cuando uses Horizon, debes utilizar el comando
horizon:forget
para eliminar un trabajo fallido en lugar del comandoqueue:forget
.
Para eliminar todos tus trabajos fallidos de la tabla failed_jobs
, puedes usar el comando queue:flush
:
php artisan queue:flush
Cuando inyectas un modelo Eloquent en un trabajo, el modelo se serializa automáticamente antes de colocarse en la cola y se vuelve a recuperar de la base de datos cuando se procesa el trabajo. Sin embargo, si el modelo se ha eliminado mientras el trabajo estaba esperando ser procesado por un trabajador, tu trabajo puede fallar con una ModelNotFoundException
.
Por conveniencia, puedes optar por eliminar automáticamente trabajos con modelos faltantes configurando la propiedad deleteWhenMissingModels
de tu trabajo en true
. Cuando esta propiedad se establece en true
, Laravel descartará silenciosamente el trabajo sin lanzar una excepción:
/** * Eliminar el trabajo si sus modelos ya no existen. * * @var bool */public $deleteWhenMissingModels = true;
Puedes podar los registros en la tabla failed_jobs
de tu aplicación invocando el comando Artisan queue:prune-failed
:
php artisan queue:prune-failed
De forma predeterminada, todos los registros de trabajos fallidos que tengan más de 24 horas se podarán. Si proporcionas la opción --hours
al comando, solo se conservarán los registros de trabajos fallidos que se insertaron en las últimas N horas. Por ejemplo, el siguiente comando eliminará todos los registros de trabajos fallidos que se insertaron hace más de 48 horas:
php artisan queue:prune-failed --hours=48
Laravel también proporciona soporte para almacenar tus registros de trabajos fallidos en DynamoDB en lugar de una tabla de base de datos relacional. Sin embargo, debes crear una tabla de DynamoDB para almacenar todos los registros de trabajos fallidos. Típicamente, esta tabla debe llamarse failed_jobs
, pero debes nombrar la tabla según el valor de la configuración queue.failed.table
dentro del archivo de configuración queue
de tu aplicación.
La tabla failed_jobs
debe tener una clave principal de partición de cadena llamada application
y una clave principal de orden de cadena llamada uuid
. La parte application
de la clave contendrá el nombre de tu aplicación según lo definido por el valor de configuración name
en el archivo de configuración app
de tu aplicación. Dado que el nombre de la aplicación es parte de la clave de la tabla DynamoDB, puedes usar la misma tabla para almacenar trabajos fallidos para múltiples aplicaciones Laravel.
Además, asegúrate de instalar el SDK de AWS para que tu aplicación Laravel pueda comunicarse con Amazon DynamoDB:
composer require aws/aws-sdk-php
A continuación, establece el valor de la opción de configuración queue.failed.driver
en dynamodb
. Además, debes definir las opciones de configuración key
, secret
, y region
dentro del array de configuración de trabajos fallidos. Estas opciones se utilizarán para autenticarse con AWS. Cuando uses el controlador dynamodb
, la opción de configuración queue.failed.database
no es necesaria:
'failed' => [ 'driver' => env('QUEUE_FAILED_DRIVER', 'dynamodb'), 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'table' => 'failed_jobs',],
Puedes indicar a Laravel que descarte trabajos fallidos sin almacenarlos configurando el valor de la opción de configuración queue.failed.driver
en null
. Típicamente, esto se puede lograr mediante la variable de entorno QUEUE_FAILED_DRIVER
:
QUEUE_FAILED_DRIVER=null
Si deseas registrar un escucha de eventos que se invocará cuando falle un trabajo, puedes usar el método failing
de la fachada Queue
. Por ejemplo, podemos adjuntar un cierre a este evento desde el método boot
de AppServiceProvider
que se incluye con Laravel:
<?php namespace App\Providers; use Illuminate\Support\Facades\Queue;use Illuminate\Support\ServiceProvider;use Illuminate\Queue\Events\JobFailed; class AppServiceProvider extends ServiceProvider{ /** * Registrar cualquier servicio de aplicación. */ public function register(): void { // ... } /** * Iniciar cualquier servicio de aplicación. */ public function boot(): void { Queue::failing(function (JobFailed $event) { // $event->connectionName // $event->job // $event->exception }); }}
Nota Cuando uses Horizon, debes utilizar el comando
horizon:clear
para borrar trabajos de la cola en lugar del comandoqueue:clear
.
Si deseas eliminar todos los trabajos de la cola predeterminada de la conexión predeterminada, puedes hacerlo usando el comando Artisan queue:clear
:
php artisan queue:clear
También puedes proporcionar el argumento connection
y la opción queue
para eliminar trabajos de una conexión y cola específicas:
php artisan queue:clear redis --queue=emails
Advertencia Borrar trabajos de las colas solo está disponible para los controladores de cola SQS, Redis y base de datos. Además, el proceso de eliminación de mensajes de SQS puede tardar hasta 60 segundos, por lo que los trabajos enviados a la cola de SQS hasta 60 segundos después de que limpies la cola también pueden eliminarse.
Si tu cola recibe un repentino aumento de trabajos, podría abrumarse, lo que conduciría a un largo tiempo de espera para que los trabajos se completen. Si lo deseas, Laravel puede alertarte cuando la cantidad de trabajos en tu cola supere un umbral especificado.
Para empezar, debes programar el comando queue:monitor
para que se ejecute cada minuto. El comando acepta los nombres de las colas que deseas supervisar, así como tu umbral deseado de cantidad de trabajos:
php artisan queue:monitor redis:default,redis:deployments --max=100
Programar este comando por sí solo no es suficiente para activar una notificación que te alerte sobre el estado abrumado de la cola. Cuando el comando encuentra una cola que tiene una cantidad de trabajos que supera tu umbral, se despachará un evento Illuminate\Queue\Events\QueueBusy
. Puedes escuchar este evento dentro del EventServiceProvider
de tu aplicación para enviar una notificación a ti o a tu equipo de desarrollo:
use App\Notifications\QueueHasLongWaitTime;use Illuminate\Queue\Events\QueueBusy;use Illuminate\Support\Facades\Event;use Illuminate\Support\Facades\Notification; /** * Registrar cualquier otro evento para tu aplicación. */public function boot(): void{ Event::listen(function (QueueBusy $event) { ->notify(new QueueHasLongWaitTime( $event->connection, $event->queue, $event->size )); });}
Al probar código que despacha trabajos, es posible que desees instruir a Laravel para que no ejecute realmente el trabajo en sí, ya que el código del trabajo se puede probar directa y separadamente del código que lo despacha. Por supuesto, para probar el trabajo en sí, puedes instanciar una instancia del trabajo e invocar el método handle
directamente en tu prueba.
Puedes usar el método fake
de la fachada Queue
para evitar que los trabajos en cola se coloquen realmente en la cola. Después de llamar al método fake
de la fachada Queue
, puedes afirmar que la aplicación intentó colocar trabajos en la cola:
<?php namespace Tests\Feature; use App\Jobs\AnotherJob;use App\Jobs\FinalJob;use App\Jobs\ShipOrder;use Illuminate\Support\Facades\Queue;use Tests\TestCase; class ExampleTest extends TestCase{ public function test_orders_can_be_shipped(): void { Queue::fake(); // Realizar envío de pedido... // Asegurar que no se hayan empujado trabajos... Queue::assertNothingPushed(); // Asegurar que se haya empujado un trabajo a una cola dada... Queue::assertPushedOn('queue-name', ShipOrder::class); // Asegurar que se haya empujado un trabajo dos veces... Queue::assertPushed(ShipOrder::class, 2); // Asegurar que no se haya empujado un trabajo... Queue::assertNotPushed(AnotherJob::class); // Asegurar que se haya empujado un Closure a la cola... Queue::assertClosurePushed(); }}
Puedes pasar un cierre a los métodos assertPushed
o assertNotPushed
para afirmar que se colocó un trabajo que pasa una determinada "prueba de verdad". Si se colocó al menos un trabajo que pasa la prueba de verdad dada, la afirmación será exitosa:
Queue::assertPushed(function (ShipOrder $job) use ($order) { return $job->order->id === $order->id;});
Si solo necesitas falsificar trabajos específicos mientras permites que otros trabajos se ejecuten normalmente, puedes pasar los nombres de clase de los trabajos que deben ser falsificados al método fake
:
public function test_orders_can_be_shipped(): void{ Queue::fake([ ShipOrder::class, ]); // Realizar envío de pedido... // Asegurar que se haya empujado un trabajo dos veces... Queue::assertPushed(ShipOrder::class, 2);}
Puedes falsificar todos los trabajos excepto un conjunto de trabajos específicos usando el método except
:
Queue::fake()->except([ ShipOrder::class,]);
Para probar cadenas de trabajos, deberás utilizar las capacidades de falsificación de la fachada Bus
. El método assertChained
de la fachada Bus
se puede usar para afirmar que se despachó una cadena de trabajos. El método assertChained
acepta como primer argumento un array de trabajos encadenados:
use App\Jobs\RecordShipment;use App\Jobs\ShipOrder;use App\Jobs\UpdateInventory;use Illuminate\Support\Facades\Bus; Bus::fake(); // ... Bus::assertChained([ ShipOrder::class, RecordShipment::class, UpdateInventory::class]);
Como se puede ver en el ejemplo anterior, el array de trabajos encadenados puede ser un array de los nombres de clase del trabajo. Sin embargo, también puedes proporcionar un array de instancias reales de trabajos. Al hacerlo, Laravel se asegurará de que las instancias de trabajos sean de la misma clase y tengan los mismos valores de propiedad que los trabajos encadenados despachados por tu aplicación:
Bus::assertChained([ new ShipOrder, new RecordShipment, new UpdateInventory,]);
Puedes usar el método assertDispatchedWithoutChain
para afirmar que se colocó un trabajo sin una cadena de trabajos:
Bus::assertDispatchedWithoutChain(ShipOrder::class);
El método assertBatched
de la fachada Bus
se puede usar para afirmar que se despachó una tanda de trabajos. El cierre proporcionado al método assertBatched
recibe una instancia de Illuminate\Bus\PendingBatch
, que se puede usar para inspeccionar los trabajos dentro de la tanda:
use Illuminate\Bus\PendingBatch;use Illuminate\Support\Facades\Bus; Bus::fake(); // ... Bus::assertBatched(function (PendingBatch $batch) { return $batch->name == 'import-csv' && $batch->jobs->count() === 10;});
Puedes usar el método assertBatchCount
para afirmar que se despachó un número determinado de tandas:
Bus::assertBatchCount(3);
Puedes usar assertNothingBatched
para afirmar que no se despacharon tandas:
Bus::assertNothingBatched();
Además, ocasionalmente es posible que necesites probar la interacción individual de un trabajo con su lote subyacente. Por ejemplo, es posible que necesites probar si un trabajo canceló further processing para su lote. Para lograr esto, debes asignar un lote falso al trabajo mediante el método withFakeBatch
. El método withFakeBatch
devuelve una tupla que contiene la instancia del trabajo y el lote falso:
[$job, $batch] = (new ShipOrder)->withFakeBatch(); $job->handle(); $this->assertTrue($batch->cancelled());$this->assertEmpty($batch->added);
Utilizando los métodos before
y after
en la fachada Queue
, puedes especificar devoluciones de llamada que se ejecuten antes o después de que se procese un trabajo en cola. Estas devoluciones de llamada son una excelente oportunidad para realizar registros adicionales o incrementar estadísticas para un panel de control. Típicamente, deberías llamar a estos métodos desde el método boot
de un proveedor de servicios. Por ejemplo, podemos usar AppServiceProvider
que se incluye con Laravel:
<?php namespace App\Providers; use Illuminate\Support\Facades\Queue;use Illuminate\Support\ServiceProvider;use Illuminate\Queue\Events\JobProcessed;use Illuminate\Queue\Events\JobProcessing; class AppServiceProvider extends ServiceProvider{ /** * Registrar cualquier servicio de aplicación. */ public function register(): void { // ... } /** * Iniciar cualquier servicio de aplicación. */ public function boot(): void { Queue::before(function (JobProcessing $event) { // $event->connectionName // $event->job // $event->job->payload() }); Queue::after(function (JobProcessed $event) { // $event->connectionName // $event->job // $event->job->payload() }); }}
Utilizando el método looping
en la fachada Queue
, puedes especificar devoluciones de llamada que se ejecuten antes de que el trabajador intente obtener un trabajo de una cola. Por ejemplo, podrías registrar un cierre para revertir cualquier transacción que haya quedado abierta debido a un trabajo fallido anterior:
use Illuminate\Support\Facades\DB;use Illuminate\Support\Facades\Queue; Queue::looping(function () { while (DB::transactionLevel() > 0) { DB::rollBack(); }});