"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Usando Supervisor para manejar la ejecución de un comando Symfony

Usando Supervisor para manejar la ejecución de un comando Symfony

Publicado el 2024-11-01
Navegar:564

Introducción

En esta publicación aprenderemos cómo usar supervisord para manejar la ejecución de un comando de Symfony. Básicamente, supervisord nos permitirá:

  • Iniciar automáticamente el comando
  • Reiniciar automáticamente el comando
  • Especifique la cantidad de procesos que queremos que inicie el supervisor.

El problema

En ocasiones recurrimos al crontab de Unix para automatizar la ejecución de procesos. Esto puede funcionar la mayor parte del tiempo, pero puede haber situaciones en las que pueda causar problemas.

Imaginemos que tenemos una tabla de base de datos que registra las notificaciones de los usuarios. La tabla almacena la siguiente información:

  • usuario
  • texto
  • canal
  • estado (ESPERANDO, ENVIADO)
  • creado en
  • actualizado en

Por otro lado, hemos codificado un comando cuya ejecución sigue los siguientes pasos:

  • Consulta las últimas notificaciones de ESPERA
  • Repite las notificaciones consultadas y:
    • Envía cada uno al usuario correspondiente.
    • Actualiza el estado de la notificación de ESPERANDO a ENVIADO

Configuramos este comando en el crontab de Linux para que se ejecute cada cierto tiempo (1 minuto, 2 minutos, etc.). Hasta ahora, todo bien.

Ahora imaginemos que el proceso actual ha consultado 500 notificaciones y cuando ha enviado 400, comienza un nuevo proceso. Esto significa que el nuevo proceso consultará las 100 notificaciones que aún no han sido actualizadas por el último proceso más las nuevas:

Using Supervisor to handle a Symfony Command execution

Esto puede hacer que esas 100 notificaciones se envíen dos veces ya que ambos procesos las han consultado.

La solución

Como solución, podemos recurrir al uso de supervisor. Mantendrá nuestro proceso en ejecución y lo reiniciará cuando sea necesario. De esta manera, solo mantenemos un proceso y evitamos superposiciones. Analicemos cómo debería verse el comando:

#[AsCommand(
    name: 'app:notification'
)]
class NotificationCommand extends Command
{
    private bool $forceFinish = false;

    protected function configure(): void
    {
        $this
            ->addOption('time-limit', null, InputOption::VALUE_OPTIONAL, 'Max time alive in seconds')
            ->addOption('time-between-calls', null, InputOption::VALUE_OPTIONAL, 'Time between every loop call')

        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->forceFinish = false;
        pcntl_signal(SIGTERM, [$this, 'signalHandler']);
        pcntl_signal(SIGINT, [$this, 'signalHandler']);

        $timeLimit = $input->getOption('time-limit');
        $timeBetweenCalls = $input->getOption('time-between-calls');
        $dtMax = (new \DateTimeImmutable())->add(\DateInterval::createFromDateString("  {$timeLimit} seconds"));

        do{
           // Here we should execute a service to query and send notifications
           // ......

           sleep($timeBetweenCalls);
           $dtCurrent = new \DateTimeImmutable();

        }while($dtCurrent forceFinish);

        return Command::SUCCESS;
    }

    public function signalHandler(int $signalNumber): void
    {
        echo 'Signal catch: ' . $signalNumber . PHP_EOL;
        match ($signalNumber) {
            SIGTERM, SIGINT => $this->forceFinish = true,
            default => null
        };
    }
}

Expliquemos el comando paso a paso:

  • El método configure declara opciones de entrada:

    • límite de tiempo: tiempo máximo que el proceso de comando puede estar activo. Después de eso, finalizará y el supervisor lo reiniciará.
    • tiempo entre llamadas: Tiempo para dormir después de cada iteración del bucle. El bucle llama al servicio que procesa las notificaciones y luego duerme durante ese tiempo.
  • El método ejecutar se comporta de la siguiente manera:

    • Establece la variable de clase forceFinish en true
    • Utiliza la biblioteca PHP pnctl para registrar el método signalHandler para manejar las señales Unix SIGTERM y SIGINT.
    • Obtiene los valores de las opciones de entrada y calcula la fecha máxima en la que el comando puede estar activo hasta que se use el valor de la opción límite de tiempo.
    • El bucle do- while realiza el código requerido para recibir las notificaciones y enviarlas (no se coloca en el comando, en su lugar hay comentarios). Luego, duerme el tiempo establecido por la opción tiempo-entre-llamadas antes de continuar.
    • Si la fecha actual (que se calcula en cada iteración del bucle) es inferior a la fecha máxima y forceFinish es falsa, el bucle continúa. De lo contrario, el comando finaliza.
  • La función signalHandler captura las señales SIGTERM y SIGINT Unix. SIGINT es la señal enviada cuando presionamos Ctrl C y SIGTERM es la señal predeterminada cuando usamos el comando kill. Cuando la función signalHandler los detecta, establece la variable forceFinish en verdadero para que, cuando termine el bucle actual, el comando finalice ya que la variable forceFinish es ya no es falso. Esto permite a los usuarios finalizar el proceso sin tener que esperar hasta que finalice la fecha máxima.

Configurar supervisor

Hasta ahora, se nos ha creado el comando. Ahora es el momento de configurar el supervisor para que pueda manejarlo. Antes de comenzar con la configuración debemos instalar supervisor. Puedes hacerlo ejecutando el siguiente comando:

sudo apt update && sudo apt install supervisor

Después de la instalación, puede asegurarse de que el supervisor se esté ejecutando ejecutando el siguiente comando:

sudo systemctl status supervisor

Los archivos de configuración del supervisor se colocan en la siguiente carpeta: /etc/supervisor/conf.d. Creemos un archivo llamado notif.conf y peguemos el siguiente contenido:

command=php /bin/console app:notifications --time-limit=120 --time-between-calls=10
user=
numprocs=1
autostart=true
autorestart=true
process_name=%(program_name)s_%(process_num)02d

Expliquemos cada clave:

  • comando: El comando para iniciar
  • usuario: El usuario de Unix que ejecuta el comando
  • numprocs: La cantidad de procesos a ejecutar
  • inicio automático: si se debe iniciar automáticamente el comando
  • inicio automático: si se debe iniciar automáticamente el comando
  • nombre_proceso: El formato del nombre del proceso Unix del comando.

Con esta configuración, el comando app:notifications se ejecutará durante un máximo de 120 segundos y se suspenderá durante 10 segundos después de cada bucle. Después de pasar 120 segundos o almacenar en caché una señal Unix, el comando saldrá del bucle y finalizará. Luego, el supervisor lo iniciará nuevamente.

Conclusión

Hemos aprendido cómo usar supervisor para mantener un comando en ejecución sin tener que usar crontab. Esto puede resultar útil cuando los procesos iniciados por el crontab pueden superponerse, provocando corrupción de datos.

En el último libro que escribí, muestro cómo usar supervisor para mantener en funcionamiento a los trabajadores de mensajería de Symfony. Si desea saber más, puede encontrar el libro aquí: Creación de una API orientada a operaciones utilizando PHP y Symfony Framework: una guía paso a paso

Declaración de liberación Este artículo se reproduce en: https://dev.to/icolomina/using-supervisor-to-handle-a-symfony-command-execution-41h7?1 Si hay alguna infracción, comuníquese con [email protected] para eliminar él
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3