Management des consumers avec Supervisor

Les consumers, également appelés workers, jouent un rôle important en développement web en optimisant l’exécution de tâches lentes et gourmandes en ressources.
Pour faciliter la gestion de ces processus, des outils comme Supervisor sont indispensables.
Cet article explique comment créer un consumer pour l’envoi asynchrone d’e-mails et comment configurer Supervisor pour gérer les processus relatifs à ce worker.

Création du consumer

Pour mettre en place le consumer, nous allons utiliser le composant Messenger de Symfony, qui supporte différents systèmes de gestion de file d’attente comme Beanstalkd ou RabbitMQ. Symfony fournit une documentation complète et détaillée sur le bundle Messenger, que vous pouvez consulter en suivant ce lien.
Avant de commencer, assurez-vous d’avoir un serveur Beanstalkd ou RabbitMQ installé et démarré sur votre système.

Création du Message et du Handler

Nous allons créer une classe MailMessage qui encapsule les données de l’e-mail à savoir l’expéditeur, le destinataire, le sujet et le corps de l’e-mail. Cette classe permettra d’instancier un e-mail à envoyer dans la file d’attente.


readonly class MailMessage
{
    public function __construct(
        private string $from,
        private string $to,
        private string $subject,
        private string $body,
    )
    {
    }

    public function getFrom(): string
    {
        return $this->from;
    }

    public function getTo(): string
    {
        return $this->to;
    }

    public function getSubject(): string
    {
        return $this->subject;
    }

    public function getBody(): string
    {
        return $this->body;
    }
}

Le handler représente le consumer qui doit tourner en permanence pour récupérer les e-mails de la file d’attente et les traiter.


#[AsMessageHandler]
readonly class MailMessageHandler
{
    public function __construct(
      private MailerInterface $mailer
    )
    {
    }

    public function __invoke(
      MailMessage $message
    ): void
    {
        try {
            $email = (new Email())
                ->from($message->getFrom())
                ->to($message->getTo())
                ->subject($message->getSubject())
                ->html($message->getBody());

            $this->mailer->send($email);

        } catch (\Throwable $e) {
            echo "Error: {$e->getMessage()}\n";
        }
    }
}
Configuration de messenger

Par défaut, Messenger traite les messages de façon immédiate. Nous allons donc configurer un bus de transport; avec cette configuration, nous spécifions que les messages seront envoyés au transport async_emailing, ce qui permet de traiter les e-mails de manière asynchrone.
N’oublier pas de définir les variables dans le fichier d’environnement.


framework:
    messenger:
        failure_transport: failed
        
        transports:
            async_emailing:
                dsn: '%env(DSN_MAILER)%'
            failed: '%env(DSN_FAILED)%'

        routing:
            App\Message\MailMessage: async_emailing

Configuration de supervisor et management du worker

Comme expliqué dans l’introduction, Supervisor est un outil essentiel pour gérer et surveiller les processus. Nous allons l’utiliser pour controller et surveiller le consumer que nous venons de mettre en place.
Pour installer Supervisor, nous exécutons la commande suivante :


sudo apt install supervisor

Nous allons ensuite créer le fichier de configuration supervisord.conf.


[program:consumer]
directory=/home/abdoulaye/Documents/docs_perso/cons_poc
command=php bin/console messenger:consume async_emailing --time-limit=3600 -vv
user=www-data
numprocs=2
startsecs = 0
autostart=true
autorestart=true
redirect_stderr=true
process_name=consumer_%(process_num)s
stdout_logfile=/var/log/mail_consumer%(process_num).log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock

[supervisord]
logfile=/dev/stdout
logfile_maxbytes=0

[unix_http_server]
file=/var/run/supervisor.sock
chmod=0700
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[include]
files = /etc/supervisor/conf.d/*.conf

Dans la configuration, nous commençons par spécifier le répertoire où se trouve notre application puis nous indiquons à Supervisor la commande à exécuter pour lancer la consommation et le traitement des e-mails. Nous définissons ensuite le nombre d’instances du consumer à exécuter en parallèle, permettant ainsi de gérer une charge de travail importante. Il est important de s’assurer aussi que le consumer se lance automatiquement au démarrage de Supervisor et puisse redémarrer en cas de plantage.
Nous configurons aussi les fichiers de logs en limitant leur taille à 10MB et en conservant les cinq dernières sauvegardes.
Le reste de la configuration concerne le daemon supervisord et sa communication avec l’interface de commande supervisorctl. Cela inclut la définition du fichier de socket Unix ainsi que d’autres paramètres globaux de Supervisor.
Pour plus d’informations sur les différents paramètres de configuration, veuillez visiter la documentation officielle de supervisor.
La configuration étant faite, nous pouvons maintenant démarrer Supervisor pour lancer les instances du consumer.


sudo supervisord -c /etc/supervisor/conf.d/supervisord.conf

supervisord_worker

Test du consumer

Pour envoyer un e-mail de manière asynchrone, il suffit maintenant de l’ajouter à la file d’attente via le bus de transport. Pour tester cela, nous implémentons une commande Symfony qui instancie un e-mail et le place dans la file d’attente. Notre consumer va alors récupérer l’e-mail et le traiter.


protected function execute(
        InputInterface  $input,
        OutputInterface $output
    ): int
    {
        $message = new MailMessage(
            'john.doe@dev.fr',
            'contact@abdoulaye-sarr.com',
            'Mail Subject',
            'This mail is processed asynchronously !'
        );

        $this->messageBus->dispatch($message);

        return Command::SUCCESS;
    }

Nous venons de créer et gérer efficacement un consumer permettant le traitement d’e-mails de manière asynchrone, en utilisant Symfony Messenger et Supervisor. En suivant cette approche, vous pouvez garantir que vos workers restent actifs et bien gérés, améliorant ainsi la performance de vos applications web.