Symfony10 min

Symfony Messenger : Patterns Avances pour la Production

Par Pierre-Arthur Demengel
SymfonyMessengerArchitecturePerformancePHP

Symfony Messenger est simple a mettre en place, mais le déployer en production avec fiabilite demande de maîtriser certains patterns avances. Voici les stratégies eprouvees pour des systèmes 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)

Après 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 après 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 vérifié 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 système fragile en infrastructure fiable et observable.

13 projets livresGrand-Est & BelgiqueLighthouse >90Disponible immédiatement

Un projet en tete ?

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

Ou appelez directement :06 95 41 30 25

WhatsApp
Appeler