En esta publicación aprenderemos cómo usar supervisord para manejar la ejecución de un comando de Symfony. Básicamente, supervisord nos permitirá:
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:
Por otro lado, hemos codificado un comando cuya ejecución sigue los siguientes pasos:
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:
Esto puede hacer que esas 100 notificaciones se envíen dos veces ya que ambos procesos las han consultado.
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:
El método ejecutar se comporta de la siguiente manera:
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.
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:
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.
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
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