1. Profundizando
  2. Colas

Introducción

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.

Conexiones vs. Colas

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

Notas y Prerrequisitos del Controlador

Base de Datos

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

Redis

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 y compression de Redis no son compatibles con el controlador de cola redis.

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 en 0 hará que los trabajadores de la cola bloqueen indefinidamente hasta que haya un trabajo disponible. Esto también evitará que se manejen señales como SIGTERM hasta que se haya procesado el próximo trabajo.

Otros Prerrequisitos del Controlador

Las siguientes dependencias son necesarias para los controladores de cola mencionados. Estas dependencias se pueden instalar a través del administrador de paquetes Composer:

  • Amazon SQS: aws/aws-sdk-php ~3.0
  • Beanstalkd: pda/pheanstalk ~4.0
  • Redis: predis/predis ~1.0 o la extensión PHP phpredis

Creación de Trabajos

Generación de Clases de Trabajo

De 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.

Estructura de Clase

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.

Inyección de Dependencias en el Método 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.

Relaciones en 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.

Trabajos Únicos

Advertencia Los trabajos únicos requieren un controlador de caché que admita bloqueos. Actualmente, los controladores de caché memcached, redis, dynamodb, database, file y array 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.

Mantener Trabajos Únicos Hasta que Comience el Procesamiento

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
{
// ...
}

Bloqueos Únicos de Trabajo

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.

Trabajos Encriptados

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
{
// ...
}

Middleware de Trabajo

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.

Limitación de Tasa

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.

Evitar Solapamientos de Trabajos

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 y array admiten bloqueos atómicos.

Compartir Claves de Bloqueo entre Clases de Trabajo

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(),
];
}
}

Limitar Excepciones

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.

Despachar Trabajos

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.

Despacho Diferido

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.

Despacho Después de que la Respuesta se Envía al Navegador

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 () {
Mail::to('[email protected]')->send(new WelcomeMessage);
})->afterResponse();

Despacho Sincrónico

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

Trabajos y Transacciones de Base de Datos

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 en true 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.

Especificar Comportamiento de Despacho en Línea

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

Encadenamiento de Trabajos

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.

Conexión y Cola de Cadena

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

Fallos de Cadena

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.

Personalización de la Cola y Conexión

Despacho a una Cola Particular

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

Despacho a una Conexión Particular

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

Especificar los Valores Máximos de Intentos / Tiempo de Espera

Intentos Máximos

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

Intentos Basados en Tiempo

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étodo retryUntil en tus escuchadores de eventos en cola.

Excepciones Máximas

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.

Tiempo de Espera

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.

Fallo por 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;

Manejo de Errores

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.

Liberación Manual de un Trabajo

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

Fallo Manual de un Trabajo

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.

Lotes de Trabajos

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

Definir Trabajos Aceptables por Lotes

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...
}
}

Despachar Lotes

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.

Nombrar Lotes

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

Conexión y Cola del Lote

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

Cadenas y Lotes

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

Agregar Trabajos a Lotes

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.

Inspeccionar Lotes

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

Devolver Lotes Desde Rutas

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

Cancelar Lotes

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];
}

Fallos de Lotes

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.

Permitir Fallos

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

Reintentar Trabajos de Lote Fallidos

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

Poda de Lotes

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

Colas de Cierre

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 llamada catch.

Ejecutar el Trabajador de Cola

El Comando 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

Ejecutar Múltiples Trabajadores de Cola

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.

Especificar la Conexión y Cola

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

Procesar un Número Especificado de Trabajos

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

Procesar Todos los Trabajos en Cola y Luego Salir

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

Procesar Trabajos Durante un Número Dado de Segundos

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

Duración del Sueño del Trabajador

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

Consideraciones de Recursos

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.

Prioridades de Cola

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

Trabajadores de Cola y Implementación

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.

Vencimientos y Tiempos de Espera del Trabajo

Vencimiento del Trabajo

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.

Tiempo de Espera del Trabajador

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 de retry_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ón retry_after, tus trabajos pueden procesarse dos veces.

Configuración de Supervisor

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.

Instalación de Supervisor

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.

Configuración de Supervisor

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)02d
command=php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=forge
numprocs=8
redirect_stderr=true
stdout_logfile=/home/forge/app.com/worker.log
stopwaitsecs=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.

Iniciar Supervisor

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.

Manejo de Trabajos Fallidos

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];
}

Limpieza Después de Trabajos Fallidos

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étodo handle se perderá.

Reintentar Trabajos Fallidos

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 comando queue:forget.

Para eliminar todos tus trabajos fallidos de la tabla failed_jobs, puedes usar el comando queue:flush:

php artisan queue:flush

Ignorar Modelos Ausentes

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;

Poda de Trabajos Fallidos

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

Almacenar Trabajos Fallidos en DynamoDB

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',
],

Desactivar Almacenamiento de Trabajos Fallidos

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

Eventos de Trabajos Fallidos

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

Borrar Trabajos de Colas

Nota Cuando uses Horizon, debes utilizar el comando horizon:clear para borrar trabajos de la cola en lugar del comando queue: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.

Monitoreo de Sus Colas

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) {
Notification::route('mail', '[email protected]')
->notify(new QueueHasLongWaitTime(
$event->connection,
$event->queue,
$event->size
));
});
}

Pruebas

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

Falsificación de un Subconjunto de Trabajos

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

Pruebas de Cadenas de Trabajo

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

Pruebas de Lotes de Trabajo

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

Interacción de Pruebas de Trabajo / Lote

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

Eventos de Trabajos

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