Отправка и получение сообщений через RabbitMQ в Symfony

от автора

Привет, Хабр!

Итак, решили внедрить асинхронные процессы в своё Symfony-приложение? Отличный выбор! А выбор RabbitMQ для этой задачи — вообще идеален: надёжный, быстрый и отлично работающий в связке с Symfony. Наша цель — научиться отправлять сообщения (скажем, сообщения о новых котиках) в очередь и плавно обрабатывать их.

Запуск RabbitMQ

Для начала понадобится сам RabbitMQ. Можно установить его напрямую или, как в большинстве проектов, с помощью Docker.

Добавляем в docker-compose.yml сервис для RabbitMQ:

version: '3.8' services:   rabbitmq:     image: rabbitmq:3-management     ports:       - "5672:5672"      # для общения с приложением       - "15672:15672"    # для панели управления     environment:       RABBITMQ_DEFAULT_USER: guest       RABBITMQ_DEFAULT_PASS: guest

Теперь запускаем:

docker-compose up -d

Проверяем: панель управления RabbitMQ доступна по адресу http://localhost:15672 (логин и пароль — guest). Здесь можно управлять очередями, обменниками и проверять, как сообщения путешествуют по RabbitMQ.

Настройка Symfony Messenger

Теперь — к нашей основной задаче. Symfony Messenger — это мощный компонент, который облегчает отправку и получение сообщений. В нашем случае, это — сообщения о котиках. С помощью Messenger будем слать котиков в очередь и обрабатывать их с другой стороны.

Чтобы начать, нужно установить пару зависимостей:

composer require symfony/messenger symfony/amqp-messenger

В файле config/packages/messenger.yaml добавляем транспорт для RabbitMQ. Это будет «транспорт асинхронных сообщений»:

framework:   messenger:     transports:       async: '%env(MESSENGER_TRANSPORT_DSN)%'  # Основной транспорт     routing:       'App\Message\CatMessage': async  # Назначаем маршрут для наших сообщений

Здесь мы указали, что все сообщения типа CatMessage будут отправляться в очередь «async».

Подключение к RabbitMQ

Добавляем следующую строку в файл .env:

MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f

Тут мы указываем параметры подключения к RabbitMQ — дефолтные логин и пароль, адрес хоста и название виртуального хоста (%2f — это код символа /, он обозначает корневой виртуальный хост).

Создание сообщения CatMessage

Теперь создадим сообщение. В Symfony это — объект, который будет содержать данные о котике. Начнем с простого примера:

// src/Message/CatMessage.php namespace App\Message;  class CatMessage {     private string $name;     private int $age;      public function __construct(string $name, int $age)     {         $this->name = $name;         $this->age = $age;     }      public function getName(): string     {         return $this->name;     }      public function getAge(): int     {         return $this->age;     } }

Теперь есть простой объект сообщения с именем и возрастом котика. Это то, что мы будем отправлять в RabbitMQ.

Обработчик сообщения

Сообщения у нас есть, но их нужно кому-то обрабатывать. Создадим обработчик, который будет получать сообщения из очереди и обрабатывать их.

// src/MessageHandler/CatMessageHandler.php namespace App\MessageHandler;  use App\Message\CatMessage; use Symfony\Component\Messenger\Handler\MessageHandlerInterface;  class CatMessageHandler implements MessageHandlerInterface {     public function __invoke(CatMessage $message)     {         // Представим, что обработчик добавляет котика в базу данных         echo sprintf("Котик %s, возраст %d лет, успешно обработан!\n", $message->getName(), $message->getAge());     } }

Наш обработчик будет просто выводить информацию о котике, но в продакшене можно здесь сделать что-то полезное, например, добавить котика в базу данных.

Отправка сообщений

Пора отправить первого котика в очередь! Для создадим контроллер, который будет принимать запрос и отправлять CatMessage через Symfony Messenger.

// src/Controller/CatController.php namespace App\Controller;  use App\Message\CatMessage; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Routing\Annotation\Route;  class CatController {     private MessageBusInterface $bus;      public function __construct(MessageBusInterface $bus)     {         $this->bus = $bus;     }      #[Route('/send-cat', name: 'send_cat')]     public function sendCat(): Response     {         $catMessage = new CatMessage('Барсик', 3);         $this->bus->dispatch($catMessage);          return new Response('Сообщение о котике отправлено!');     } }

Теперь можно перейти по адресу /send-cat, и сообщение отправится в очередь RabbitMQ. Каждый раз, когда этот маршрут вызывается, новый котик добавляется в очередь.

Запуск консумера (обработчика)

Чтобы Symfony мог начать забирать сообщения из RabbitMQ и передавать их в обработчик, нужно запустить консумер:

php bin/console messenger:consume async

Теперь при каждом новом сообщении обработчик будет выводить информацию о котике.

Обработка ошибок и ретраи

Жизнь сурова: иногда обработка сообщений может завершиться с ошибкой. Symfony Messenger позволяет настраивать ретраи и логировать ошибки.

Для этого в messenger.yaml добавим параметры:

framework:   messenger:     failure_transport: failed      transports:       async: '%env(MESSENGER_TRANSPORT_DSN)%'       failed: 'doctrine://default?queue_name=failed'      retry_strategy:       async:         max_retries: 3         delay: 1000    # Задержка между ретраями         multiplier: 2  # Увеличиваем задержку на 2 раза         max_delay: 30000  # Максимальная задержка между ретраями

Здесь настроили стратегию ретраев, которая повторит отправку сообщения до трех раз с увеличением задержки. Если сообщение так и не будет обработано, оно переместится в failed-транспорт, где мы сможем его повторно отправить позже.

Инлайн-классы

Если хочется экспериментировать с инлайн-классами (например, в тестовых сценариях), вы можно создать обработчик с помощью анонимных классов:

$handler = new class implements MessageHandlerInterface {     public function __invoke(CatMessage $message)     {         echo sprintf("Инлайн-обработчик: Котик %s, возраст %d лет, был обработан.\n", $message->getName(), $message->getAge());     } };

Инлайн-классы полезны для быстрого прототипирования, но не стоит злоупотреблять!


Если у вас есть чем дополнить или поделиться своим опытом работы с RabbitMQ в Symfony — пишите в комментариях! Всегда интересно узнать.

Также спешу напомнить, что сегодня, 12 ноября, в 20:00 пройдет открытый урок на тему «Надёжная отправка и получение сообщений через RabbitMQ в Symfony». Если интересно, узнать подробности и записаться можно на странице курса «Symfony Framework».


ссылка на оригинал статьи https://habr.com/ru/articles/856238/