Preparando nuestro producto para escalar con las colas de Laravel

Se preparó una traducción del artículo especialmente para los estudiantes del curso "Marco Laravel" .




Hola, soy Valerio, un ingeniero de software de Italia.

Esta guía está destinada a todos los desarrolladores de PHP que ya tienen aplicaciones en línea con usuarios reales, pero que carecen de una comprensión más profunda de cómo implementar (o mejorar significativamente) la escalabilidad en su sistema utilizando colas Laravel. Me enteré de Laravel a finales de 2013 al comienzo de la quinta versión del marco. Entonces aún no era un desarrollador involucrado en proyectos serios, y uno de los aspectos de los marcos modernos, especialmente en Laravel, que me parecía el más incomprensible, eran las Colas.

Leyendo la documentación, adiviné sobre el potencial, pero sin una experiencia de desarrollo real, todo esto se mantuvo solo en el nivel de las teorías en mi cabeza.
Hoy en día, soy el creador Inspector.dev- tablero de mandos en tiempo real , que realiza miles de tareas cada hora, y entiendo esta arquitectura mucho mejor que antes.



En este artículo, le diré cómo descubrí las colas y las tareas y qué configuraciones me ayudaron a procesar grandes cantidades de datos en tiempo real, mientras ahorraba recursos del servidor.

Introducción


Cuando una aplicación PHP recibe una solicitud HTTP entrante, nuestro código se ejecuta secuencialmente paso a paso hasta que se completa la solicitud y se devuelve una respuesta al cliente (por ejemplo, el navegador del usuario). Este comportamiento sincrónico es verdaderamente intuitivo, predecible y fácil de entender. Envío una solicitud http al punto final, la aplicación extrae los datos de la base de datos, los convierte al formato apropiado, realiza algunas tareas adicionales y las devuelve. Todo sucede linealmente.

Las tareas y las colas introducen un comportamiento asincrónico que viola este flujo de línea. Es por eso que estas características me parecieron un poco extrañas al principio.
Pero a veces una solicitud HTTP entrante puede desencadenar un ciclo de tareas que requieren mucho tiempo, por ejemplo, enviar notificaciones por correo electrónico a todos los miembros del equipo. Esto puede significar enviar seis o diez correos electrónicos, lo que puede demorar cuatro o cinco segundos. Por lo tanto, cada vez que el usuario hace clic en el botón correspondiente, debe esperar cinco segundos antes de poder seguir usando la aplicación.

Cuanto más crece la aplicación, peor es este problema.

¿Qué es una tarea?


Un trabajo es una clase que implementa el método de manejo que contiene la lógica que queremos ejecutar de forma asincrónica.

<?php

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Bus\Queueable;


class CallExternalAPI implements ShouldQueue
{
    use Dispatchable,
        InteractsWithQueue,
        Queueable;
        
    /**
     * @var string
     */
    protected $url;

    /**
     *    .
     *
     * @param array $Data
     */
    public function __construct($url)
    {
        $this->url = $url;
    }
    

    /**
     *  ,   .
     *
     * @return void
     * @throws \Throwable
     */
    public function handle()
    {
        file_get_contents($this->url);
    }
}

Como se mencionó anteriormente, la razón principal para concluir un fragmento de código en Job es completar una tarea que requiere mucho tiempo, sin obligar al usuario a esperar a que se complete.

¿Qué queremos decir con "tareas laboriosas"?


Esta es una pregunta natural. El envío de correos electrónicos es el ejemplo más común utilizado en artículos que hablan sobre colas, pero quiero contarles sobre la experiencia real de lo que tenía que hacer.

Como propietario de un producto, es muy importante para mí sincronizar la información de viaje del usuario con nuestras herramientas de marketing y atención al cliente. Por lo tanto, en función de las acciones del usuario, actualizamos la información del usuario en varios software externos a través de API (o llamadas http externas) para fines de servicio y marketing.
Uno de los puntos finales más utilizados en mi aplicación puede enviar 10 correos electrónicos y hacer 3 llamadas http a servicios externos antes de su finalización. Ningún usuario esperará tanto tiempo; lo más probable es que simplemente dejen de usar mi aplicación.

Gracias a las colas, puedo encapsular todas estas tareas en las clases asignadas, transferir la información necesaria para la ejecución de su trabajo al constructor y programar que se completen en una fecha posterior en segundo plano para que mi controlador pueda devolver la respuesta de inmediato.

<?php

class ProjectController 
{
    public function store(Request $request)
    {
        $project = Project::create($request->all());
        
        //  NotifyMembers, TagUserActive, NotifyToProveSource 
        //   ,     
        Notification::queue(new NotifyMembers($project->owners));
        $this->dispatch(new TagUserAsActive($project->owners));
        $this->dispatch(new NotifyToProveSource($project->owners));
        
        return $project;
    }
}

No necesito esperar la finalización de todos estos procesos antes de devolver la respuesta; por el contrario, la espera será igual al tiempo requerido para publicarlos en la cola. ¡Y eso significa la diferencia entre 10 segundos y 10 milisegundos!

¿Quién realiza estas tareas después de enviarlas a la cola?


Esta es la arquitectura clásica de editor / consumidor . Ya hemos publicado nuestras tareas en la cola desde el controlador, ahora vamos a entender cómo se usa la cola y, finalmente, se realizan las tareas.



Para usar la cola, necesitamos ejecutar uno de los comandos artesanales más populares:

php artisan queue:work

Como dice la documentación:
Laravel incluye un trabajador de cola que procesa nuevas tareas cuando se ponen en cola.

¡Excelente! Laravel proporciona una interfaz preparada para las tareas de colas y un comando listo para usar para recuperar tareas de la cola y ejecutar su código en segundo plano.

Rol del supervisor


Era otra "cosa extraña" al principio. Creo que es normal descubrir cosas nuevas. Después de esta etapa de capacitación, escribo estos artículos para ayudarme a organizar mis habilidades y, al mismo tiempo, ayudar a otros desarrolladores a ampliar sus conocimientos.

Si la tarea falla mientras se produce una excepción, el equipo queue:workdetendrá su trabajo.

Para que un proceso se queue:workejecute continuamente (consumiendo sus colas), debe usar un monitor de proceso como Supervisor para asegurarse de que el comando queue:workno deje de funcionar, incluso si la tarea arroja una excepción. Supervisor reinicia el comando después de que falla, comenzando nuevamente desde la siguiente tarea, dejando de lado la excepción de lanzamiento.

Las tareas se ejecutarán en segundo plano en su servidor, ya no dependen de la solicitud HTTP. Esto introduce algunos cambios que tuve que considerar al implementar el código de tarea.

Estos son los más importantes a los que prestaré atención:

¿Cómo puedo saber que el código de la tarea no funciona?


Cuando se ejecuta en segundo plano, no puede notar de inmediato que su tarea está causando errores.
Ya no tendrá comentarios inmediatos, como cuando realiza una solicitud http desde su navegador. Si la tarea falla, lo hará en silencio y nadie se dará cuenta.

Considere integrar una herramienta de monitoreo en tiempo real como el Inspector para detectar cada falla.

No tienes una solicitud http


No hay más solicitudes HTTP. Su código se ejecutará desde cli.

Si necesita parámetros de consulta para completar sus tareas, debe pasarlos al constructor de tareas para su uso posterior en tiempo de ejecución:

<?php

//   

class TagUserJob implements ShouldQueue
{
    public $data;
    
    public function __construct(array $data)
    {
        $this->data = $data;
    }
}

//       

$this->dispatch(new TagUserJob($request->all()));

No sabes quién inició sesión


Ya no hay una sesión . Del mismo modo, no conocerá la identidad del usuario que ha iniciado sesión, por lo que si necesita información del usuario para completar la tarea, debe pasar el objeto de usuario al constructor de la tarea:

<?php

//   
class TagUserJob implements ShouldQueue
{
    public $user;
    
    public function __construct(User $user)
    {
        $this->user= $user;
    }
}

//       
$this->dispatch(new TagUserJob($request->user()));

Comprender cómo escalar


Desafortunadamente, en muchos casos esto no es suficiente. Usar una línea y un consumidor pronto puede volverse inútil.

Las colas son búferes FIFO ("primero en entrar, primero en salir"). Si ha planeado muchas tareas, tal vez incluso de diferentes tipos, deben esperar a que otros completen sus tareas antes de completarlas.

Hay dos formas de escalar:

varios consumidores por cola



, por lo que se eliminarán cinco tareas de la cola a la vez, lo que acelerará el procesamiento de la cola.

Colas para propósitos especiales

También puede crear colas específicas para cada tipo de tarea que se lanzará con un consumidor dedicado para cada cola.



Por lo tanto, cada cola se procesará de forma independiente, sin esperar a que se completen otros tipos de tareas.

Horizonte


Laravel Horizon es un administrador de colas que le brinda control total sobre cuántas colas desea configurar y la capacidad de organizar a los consumidores, lo que permite a los desarrolladores combinar estas dos estrategias e implementar la que mejor se adapte a sus necesidades de escalabilidad.

El lanzamiento se produce a través de php artisan horizon en lugar de php artisan queue: work . Este comando escanea su archivo de configuración horizon.phpe inicia una serie de trabajadores de cola dependiendo de la configuración:

<?php

'production' => [
    'supervisor-1' => [
        'connection' => "redis",
        'queue' => ['adveritisement', 'logs', 'phones'],
        'processes' => 9,
        'tries' => 3,
        'balance' => 'simple', //   simple, auto  null

    ]
]

En el ejemplo anterior, Horizon comenzará tres colas con tres procesos asignados para procesar cada cola. Como se menciona en la documentación de Laravel, el enfoque basado en código de Horizon permite que mi configuración permanezca en un sistema de control de versiones donde mi equipo puede colaborar. Esta es la solución perfecta usando una herramienta de CI.

Para conocer el significado de los parámetros de configuración en detalle, lea este maravilloso artículo .

Mi propia configuracion



<?php

'production' => [
    'supervisor-1' => [
        'connection' => 'redis',
        'queue' => ['default', 'ingest', 'notifications'],
        'balance' => 'auto',
        'processes' => 15,
        'tries' => 3,
    ],
]

El inspector utiliza principalmente tres colas:

  • ingerir procesos para analizar datos de aplicaciones externas;
  • las notificaciones se utilizan para programar notificaciones de inmediato si se detecta un error al recibir datos;
  • el valor predeterminado se usa para otras tareas que no quiero mezclar con procesos de ingesta y notificaciones .

Mediante el uso de balance=autoHorizon, comprende que el número máximo de procesos activados es 15, que se distribuirán dinámicamente según la carga de las colas.

Si las colas están vacías, Horizon admite un proceso activo para cada cola, lo que permite al consumidor procesar inmediatamente la cola si se programa una tarea.

Observaciones finales


La ejecución simultánea en segundo plano puede causar muchos otros errores impredecibles, como el "Tiempo de espera de bloqueo de MySQL" y muchos otros problemas de diseño. Lee más aquí .

Espero que este artículo te haya ayudado a usar colas y tareas con más confianza. Si desea saber más acerca de nosotros, visite nuestro sitio web en www.inspector.dev

Publicado anteriormente aquí www.inspector.dev/what-worked-for-me-using-laravel-queues-from-the-basics-to -horizonte



Entra en el curso y obtén un descuento.



All Articles