Сервис уведомлений нашего магазина должен рассылать письма: «заказ оплачен», «товар отправлен». Писем много, отправляются они медленно, а терять их нельзя. В прошлом уроке мы выяснили, что для такой работы нужен брокер сообщений. Сегодня поднимем RabbitMQ, напишем на Node.js первого отправителя (producer) и получателя (consumer) и посмотрим, как брокер не теряет сообщения, когда воркер падает.

Что такое RabbitMQ

RabbitMQ — один из самых популярных брокеров сообщений с открытым кодом. Он написан на Erlang, работает как отдельный сервер и общается с приложениями по протоколу AMQP 0-9-1 (поддерживает и другие, например MQTT). Клиентские библиотеки есть для всех популярных языков, поэтому сервисы на Node.js, Python и Java обмениваются сообщениями через один брокер. В Node.js мы будем использовать библиотеку amqplib.

Модель AMQP: пять деталей

Вспомните почту: вы опускаете письмо в ящик, сортировочный центр решает, куда его отправить, а почтальон доставляет адресату. В AMQP устройство такое же:

  • Producer — приложение, которое публикует сообщения.
  • Exchange — «сортировочный центр»: принимает сообщение и решает, в какие очереди его положить. Сам сообщения не хранит.
  • Binding — правило «exchange → очередь» с ключом привязки (binding key).
  • Queue — очередь: хранит сообщения, пока их не заберут.
  • Consumer — приложение, которое забирает сообщения из очереди и обрабатывает.
Сообщение проходит путь producer, exchange, binding, очередь, consumer, а затем ack
Сообщение проходит путь producer, exchange, binding, очередь, consumer, а затем ack

Важно: producer никогда не пишет в очередь напрямую, только в exchange. Но у RabbitMQ есть безымянный exchange по умолчанию (default exchange, имя — пустая строка): каждая очередь автоматически привязана к нему под своим именем. Поэтому channel.sendToQueue('emails', body) — сокращение для channel.publish('', 'emails', body). Остальные типы exchange разберём в следующем уроке.

Запускаем RabbitMQ в Docker

Вы уже запускали PostgreSQL и Redis через Docker Compose, RabbitMQ запускается так же. Нужен образ с суффиксом management: в него встроен веб-интерфейс.

yaml
services:
  rabbitmq:
    image: rabbitmq:4-management
    hostname: rabbitmq
    ports:
      - "5672:5672"     # AMQP: applications connect here
      - "15672:15672"   # management UI in the browser
    volumes:
      - rabbitmq-data:/var/lib/rabbitmq

volumes:
  rabbitmq-data:

Порт 5672 нужен приложениям (AMQP), порт 15672 — веб-интерфейсу. Параметр hostname задан не случайно: RabbitMQ хранит данные под именем узла, которое берёт из имени хоста, и без фиксированного имени после пересоздания контейнера прежние данные не найдутся. Запускаем брокер и ставим клиентскую библиотеку:

bash
docker compose up -d
npm install amqplib