"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Usando o Supervisor para lidar com a execução de um comando Symfony

Usando o Supervisor para lidar com a execução de um comando Symfony

Publicado em 01/11/2024
Navegar:363

Introdução

Neste post aprenderemos como usar o supervisord para lidar com a execução de um comando symfony. Basicamente, o supervisord nos permitirá:

  • Iniciar automaticamente o comando
  • Reiniciar automaticamente o comando
  • Especifique o número de processos que queremos que o supervisor inicie.

O problema

Às vezes recorremos ao crontab unix para automatizar a execução de processos. Isso pode funcionar na maioria das vezes, mas pode haver situações em que pode causar problemas.

Vamos imaginar que temos uma tabela de banco de dados que registra as notificações dos usuários. A tabela armazena as seguintes informações:

  • usuário
  • texto
  • canal
  • status (AGUARDANDO, ENVIADO)
  • criadoEm
  • atualizadoem

Por outro lado, codificamos um comando cuja execução segue os seguintes passos:

  • Consulta as últimas notificações WAITING
  • Faz um loop nas notificações consultadas e:
    • Envia cada um para o usuário correspondente.
    • Atualiza o status da notificação de AGUARDANDO para ENVIADO

Definimos este comando no crontab do Linux para ser executado de vez em quando (1 minuto, 2 minutos, etc.). Até agora tudo bem.

Agora vamos imaginar que o processo atual consultou 500 notificações e quando enviou 400, um novo processo é iniciado. Isso significa que o novo processo irá consultar as 100 notificações que ainda não foram atualizadas pelo último processo mais as novas:

Using Supervisor to handle a Symfony Command execution

Isso pode fazer com que essas 100 notificações sejam enviadas duas vezes, pois ambos os processos as consultaram.

A solução

Como solução, podemos recorrer ao uso do supervisor. Ele manterá nosso processo em execução e o reiniciará quando necessário. Dessa forma, mantemos apenas um processo e evitamos sobreposições. Vamos analisar como deve ficar o 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
        };
    }
}

Vamos explicar o comando passo a passo:

  • O método configure declara as opções de entrada:

    • time-limit: Tempo máximo que o processo de comando pode estar ativo. Depois disso, ele será finalizado e o supervisor irá reiniciá-lo.
    • tempo entre chamadas: Tempo para dormir após cada iteração do loop. O loop chama o serviço que processa as notificações e depois dorme durante esse período.
  • O método execute se comporta da seguinte forma:

    • Define a variável de classe forceFinish como true
    • Usa a biblioteca PHP pnctl para registrar o método signalHandler para manipular os sinais Unix SIGTERM e SIGINT.
    • Obtém os valores das opções de entrada e calcula a data máxima em que o comando pode estar ativo até usar o valor da opção time-limit.
    • O loop do-while executa o código necessário para receber as notificações e enviá-las (não é colocado no comando, em vez disso há comentários). Em seguida, ele dorme o tempo estabelecido pela opção tempo entre chamadas antes de continuar.
    • Se a data atual (que é calculada em cada iteração do loop) for menor que a data máxima e o forceFinish for falso, o loop continua. Caso contrário, o comando termina.
  • A função signalHandler captura os sinais SIGTERM e SIGINT Unix. SIGINT é o sinal enviado quando pressionamos Ctrl C e SIGTERM é o sinal padrão quando usamos o comando kill. Quando a função signalHandler os detecta, ela define a variável forceFinish como true para que, quando o loop atual terminar, o comando termine, pois a variável forceFinish é não é mais falso. Isso permite que os usuários encerre o processo sem ter que esperar até que a data máxima termine.

Configurando o Supervisor

Até agora, criamos o comando. Agora é hora de configurar o supervisor para que ele possa lidar com isso. Antes de iniciar a configuração, devemos instalar o supervisor. Você pode fazer isso executando o seguinte comando:

sudo apt update && sudo apt install supervisor

Após a instalação, você pode garantir que o supervisor esteja em execução executando o próximo comando:

sudo systemctl status supervisor

Os arquivos de configuração do Supervisor são colocados na seguinte pasta: /etc/supervisor/conf.d. Vamos criar um arquivo chamado notif.conf e colar o seguinte conteúdo:

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

Vamos explicar cada chave:

  • comando: O comando para iniciar
  • user: O usuário unix que executa o comando
  • numprocs: O número de processos a serem executados
  • autostart: se deseja executar o comando de inicialização automática
  • autostart: comando de reinicialização automática
  • process_name: O formato do nome do processo unix do comando.

Com esta configuração, o comando app:notifications ficará rodando por no máximo 120 segundos e ficará suspenso por 10 segundos após cada loop. Depois de passar 120 segundos ou armazenar em cache um sinal unix, o comando sairá do loop e terminará. Em seguida, o supervisor iniciará novamente.

Conclusão

Aprendemos como usar o supervisor para manter um comando em execução sem precisar usar o crontab. Isso pode ser útil quando os processos iniciados pelo crontab podem se sobrepor, causando corrupção de dados.

No último livro que escrevi, mostro como usar o supervisor para manter os trabalhadores do symfony messenger funcionando. Se quiser saber mais, você pode encontrar o livro aqui: Construindo uma API Orientada a Operações usando PHP e o Symfony Framework: Um guia passo a passo

Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/icolomina/using-supervisor-to-handle-a-symfony-command-execution-41h7?1 Se houver alguma violação, entre em contato com [email protected] para excluir isto
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3