Symfony10 min

Symfony Messenger : Patterns Avances pour la Production

Par Pierre-Arthur Demengel
SymfonyMessengerArchitecturePerformancePHP
Partager

Symfony Messenger est simple à mettre en place, mais le deployer en production avec fiabilite demande de maitriser certains patterns avances. Voici les strategies eprouvees pour des systemes robustes.

1. Retry Strategy intelligente

La configuration par defaut retente 3 fois avec un delai exponentiel. En production, affinez selon le type d'erreur :

# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                retry_strategy:
                    max_retries: 5
                    delay: 1000
                    multiplier: 3
                    max_delay: 60000
            async_priority_high:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                options:
                    queue_name: high
                retry_strategy:
                    max_retries: 10
                    delay: 500
                    multiplier: 2

Retry conditionnel

Certaines erreurs ne meritent pas de retry (validation, 404...). Utilisez un RecoverableExceptionInterface :

use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;

#[AsMessageHandler]
class PaymentHandler
{
    public function __invoke(ProcessPayment $message): void
    {
        try {
            $this->gateway->charge($message->amount);
        } catch (InvalidCardException $e) {
            // Ne pas retenter : erreur permanente
            throw new UnrecoverableMessageHandlingException($e->getMessage(), 0, $e);
        }
        // Les autres exceptions seront retentees automatiquement
    }
}

2. Dead Letter Queue (DLQ)

Apres epuisement des retries, les messages atterrissent dans la failure transport. Mettez en place un monitoring :

framework:
    messenger:
        failure_transport: failed

        transports:
            failed:
                dsn: 'doctrine://default?queue_name=failed'
# Consulter les messages echoues
php bin/console messenger:failed:show

# Retenter un message specifique
php bin/console messenger:failed:retry 42

# Supprimer un message
php bin/console messenger:failed:remove 42

Alerting automatique

#[AsMessageHandler]
class FailedMessageAlertHandler
{
    public function __construct(private NotifierInterface $notifier) {}

    public function __invoke(FailedMessageEvent $event): void
    {
        $notification = new Notification(
            sprintf('Message echoue : %s', get_class($event->getEnvelope()->getMessage())),
            ['chat/slack']
        );
        $this->notifier->send($notification);
    }
}

3. Middleware custom

Les middlewares interceptent chaque message avant et apres le handler :

class AuditMiddleware implements MiddlewareInterface
{
    public function __construct(private LoggerInterface $logger) {}

    public function handle(Envelope $envelope, StackInterface $stack): Envelope
    {
        $message = $envelope->getMessage();
        $this->logger->info('Processing {class}', [
            'class' => get_class($message),
            'id' => $envelope->last(TransportMessageIdStamp::class)?->getId(),
        ]);

        $startTime = microtime(true);

        try {
            $envelope = $stack->next()->handle($envelope, $stack);
        } finally {
            $duration = microtime(true) - $startTime;
            $this->logger->info('Processed in {duration}ms', [
                'duration' => round($duration * 1000, 2),
            ]);
        }

        return $envelope;
    }
}

4. Scaling : workers et supervision

Supervisord

[program:messenger-worker]
command=php /var/www/app/bin/console messenger:consume async --time-limit=3600 --memory-limit=256M
autostart=true
autorestart=true
numprocs=3
process_name=%(program_name)s_%(process_num)02d
stdout_logfile=/var/log/messenger_%(process_num)02d.log

Signals et graceful shutdown

Les options --time-limit et --memory-limit permettent un recyclage propre des workers. Combinez avec pcntl_signal pour un arret graceful :

# Le worker finit le message en cours avant de s'arreter
php bin/console messenger:consume async --time-limit=3600

5. Patterns de serialisation

Messages versionnes

Pour evoluer vos messages sans casser les workers en cours :

// V1
class OrderPlaced {
    public function __construct(public string $orderId) {}
}

// V2 - compatible backward
class OrderPlaced {
    public function __construct(
        public string $orderId,
        public ?string $channel = null, // nouveau champ, nullable
    ) {}
}

Serialisation custom pour l'interop

framework:
    messenger:
        transports:
            external_events:
                dsn: '%env(RABBITMQ_DSN)%'
                serializer: App\Messenger\ExternalEventSerializer

6. Monitoring en production

  • Prometheus + Grafana : metriques custom via EventSubscriber sur les events Messenger
  • Symfony Profiler : panel Messenger pour le dev
  • Healthcheck : endpoint qui verifie que les workers consomment
// Healthcheck simple
#[Route('/health/messenger')]
class MessengerHealthController
{
    public function __invoke(Connection $connection): JsonResponse
    {
        $count = $connection->executeQuery(
            "SELECT COUNT(*) FROM messenger_messages WHERE delivered_at IS NULL AND available_at <= NOW()"
        )->fetchOne();

        return new JsonResponse([
            'pending_messages' => $count,
            'healthy' => $count < 1000,
        ], $count < 1000 ? 200 : 503);
    }
}

Conclusion

Messenger en production, c'est : retry intelligent, DLQ avec monitoring, workers supervises avec limites, et serialisation versionnee. Ces patterns transforment un systeme fragile en infrastructure fiable et observable.

Partager
13 projets livrésGrand-Est & BelgiqueLighthouse >90Disponible immédiatement

Un projet en tête ?

Discutons de votre site web. Réponse garantie sous 24h.

Ou appelez directement :06 95 41 30 25

WhatsApp
Appeler