RabbitMQ в Docker Compose: поднимаем брокер за 2 минуты
RabbitMQ в Docker Compose: поднимаем брокер
RabbitMQ - один из самых популярных message broker'ов. Поддерживает AMQP 0-9-1, а для Go есть отличная библиотека amqp091-go. См. также bus vs broker.
Docker Compose: минимальный сетап
services:
rabbitmq:
image: rabbitmq:3-management-alpine
container_name: rabbitmq
ports:
- "5672:5672" # AMQP протокол
- "15672:15672" # Management UI
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
volumes:
- rabbitmq_data:/var/lib/rabbitmq
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
rabbitmq_data:
После docker compose up -d админка доступна на http://localhost:15672 (guest/guest).
Три ключевых понятия
┌──────────┐ routing key ┌───────────┐
│ Producer │ ──────────────→ │ Exchange │
└──────────┘ └─────┬─────┘
│ binding
┌─────▼─────┐
│ Queue │ ──→ Consumer
└───────────┘
- Exchange - принимает сообщения от producer и маршрутизирует их в очереди
- Queue - хранит сообщения до обработки consumer'ом
- Routing key - ключ маршрутизации: exchange решает, в какую очередь отправить
Producer никогда не отправляет напрямую в queue - всегда через exchange.
Типы Exchange
Тип Маршрутизация Пример
──────── ────────────────────────── ─────────────────────
direct Точное совпадение routing key routing_key="order.created"
→ queue с таким же binding
topic Паттерн с * и # "lesson.*" → lesson.completed,
lesson.started
"*.error" → payment.error,
auth.error
fanout Во ВСЕ привязанные очереди Broadcast: уведомления,
(routing key игнорируется) логирование
headers По заголовкам сообщения Редко используется
Для event-driven архитектуры topic exchange - самый гибкий выбор.
Topic Exchange: паттерны маршрутизации
Символ Значение Пример
────── ────────── ─────────────────────
* Одно слово lesson.* → lesson.completed ✓
→ lesson.started ✓
→ lesson.quiz.passed ✗
# Ноль или более слов lesson.# → lesson.completed
→ lesson.quiz.passed ✓
→ lesson ✓
Пример конфигурации:
Exchange: events (topic)
├── Binding: lesson.* → queue: progress-worker
├── Binding: lesson.* → queue: analytics-worker
├── Binding: *.completed → queue: achievements-worker
└── Binding: # → queue: audit-log (всё)
Настройка из кода
import amqp "github.com/rabbitmq/amqp091-go"
func SetupRabbitMQ(conn *amqp.Connection) (*amqp.Channel, error) {
ch, err := conn.Channel()
if err != nil {
return nil, fmt.Errorf("open channel: %w", err)
}
// Создаём exchange
err = ch.ExchangeDeclare(
"events", // name
"topic", // type
true, // durable (переживёт рестарт)
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
if err != nil {
return nil, fmt.Errorf("declare exchange: %w", err)
}
// Создаём очереди
queues := []struct {
name string
routingKey string
}{
{"progress-worker", "lesson.*"},
{"analytics-worker", "lesson.*"},
{"email-worker", "lesson.completed"},
}
for _, q := range queues {
queue, err := ch.QueueDeclare(
q.name,
true, // durable
false, // auto-delete
false, // exclusive
false, // no-wait
nil,
)
if err != nil {
return nil, fmt.Errorf("declare queue %s: %w", q.name, err)
}
err = ch.QueueBind(
queue.Name,
q.routingKey,
"events",
false,
nil,
)
if err != nil {
return nil, fmt.Errorf("bind queue %s: %w", q.name, err)
}
}
return ch, nil
}
<?php
declare(strict_types=1);
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Channel\AMQPChannel;
final class RabbitMqTopology
{
public function __construct(
private readonly AMQPStreamConnection $conn,
) {}
public function setup(): AMQPChannel
{
$ch = $this->conn->channel();
// Создаём exchange
$ch->exchange_declare(
exchange: 'events',
type: 'topic',
passive: false,
durable: true, // переживёт рестарт
auto_delete: false,
);
// Создаём очереди + биндинги
$queues = [
['name' => 'progress-worker', 'routingKey' => 'lesson.*'],
['name' => 'analytics-worker', 'routingKey' => 'lesson.*'],
['name' => 'email-worker', 'routingKey' => 'lesson.completed'],
];
foreach ($queues as $q) {
$ch->queue_declare(
queue: $q['name'],
passive: false,
durable: true,
exclusive: false,
auto_delete: false,
);
$ch->queue_bind(
queue: $q['name'],
exchange: 'events',
routing_key: $q['routingKey'],
);
}
return $ch;
}
}
В Symfony Messenger вместо ручной декларации топологии используют AmqpTransport (config/packages/messenger.yaml) - он сам создаёт exchange и очереди при первой публикации.
Management UI
Админка на localhost:15672 показывает:
Overview - общая статистика: сообщений в очередях, скорость публикации
Connections - кто подключён, сколько каналов
Channels - активные каналы, prefetch count
Exchanges - список exchange, биндинги
Queues - очереди, количество сообщений, consumer'ы
Полезные действия в UI:
- Опубликовать тестовое сообщение в exchange
- Посмотреть сообщение из очереди (Get Messages)
- Проверить биндинги между exchange и queue
- Мониторить скорость publish/deliver
Подключение из сервиса
func ConnectRabbitMQ(url string) (*amqp.Connection, error) {
// url формат: amqp://guest:guest@localhost:5672/
conn, err := amqp.Dial(url)
if err != nil {
return nil, fmt.Errorf("dial rabbitmq: %w", err)
}
return conn, nil
}
// В docker-compose зависимый сервис:
// services:
// app:
// depends_on:
// rabbitmq:
// condition: service_healthy
// environment:
// RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/
<?php
declare(strict_types=1);
use PhpAmqpLib\Connection\AMQPStreamConnection;
final class RabbitMqConnector
{
public function connect(string $host, int $port, string $user, string $pass): AMQPStreamConnection
{
// dsn-эквивалент: amqp://guest:guest@rabbitmq:5672/
return new AMQPStreamConnection($host, $port, $user, $pass);
}
}
// В docker-compose зависимый сервис:
// services:
// app:
// depends_on:
// rabbitmq:
// condition: service_healthy
// environment:
// MESSENGER_TRANSPORT_DSN: amqp://guest:guest@rabbitmq:5672/%2f/events
- Message Brokers - Kafka в Docker Compose - Kafka как альтернатива RabbitMQ: когда брать Kafka вместо AMQP
Мини-задание
- Подними RabbitMQ через Docker Compose с healthcheck
- Открой админку, создай exchange
events(type: topic) - Создай две очереди:
analyticsиnotifications - Привяжи
analyticsпо ключуlesson.*, аnotificationsпо ключуlesson.completed - Опубликуй тестовое сообщение через UI и проверь, в какие очереди оно попало