Ответ
Маршрутизация сообщений в RabbitMQ определяется взаимодействием трех сущностей: издатель (Publisher), обменник (Exchange) и очереди (Queues). Издатель никогда не отправляет сообщения напрямую в очередь, а всегда через обменник, используя ключ маршрутизации (Routing Key).
Ключевые компоненты:
- Exchange (Обменник): Точка входа для сообщений. Получает сообщение от издателя и решает, в какие очереди его направить, основываясь на своем типе и ключе маршрутизации.
- Binding (Привязка): "Правило", которое связывает обменник с очередью. Для некоторых типов обменников включает ключ привязки (Binding Key), который сравнивается с Routing Key.
- Routing Key (Ключ маршрутизации): Строка, которую издатель прикрепляет к сообщению. Это "адрес" или "метка" для маршрутизации.
- Queue (Очередь): Хранилище сообщений, откуда их забирают потребители (Consumers).
Типы обменников и логика маршрутизации:
Тип Exchange (ExchangeType) |
Логика маршрутизации | Типичное использование |
|---|---|---|
Direct |
Сообщение идет в очереди, чей Binding Key ТОЧНО совпадает с Routing Key. | Точечная отправка, RPC, распределение задач по типам. |
Fanout |
Игнорирует Routing Key. Копия сообщения отправляется во все привязанные очереди. | Широковещательные уведомления, событийная рассылка (например, "пользователь зарегистрировался"). |
Topic |
Использует шаблоны в Binding Key (* — одно слово, # — ноль или несколько слов). Сообщение попадает в очереди, чей шаблон совпадает с Routing Key. |
Сложная маршрутизация событий по категориям (например, logs.app.error, orders.eu.paid). |
Headers |
Игнорирует Routing Key. Маршрутизация происходит на основе заголовков (headers) сообщения, где указываются пары ключ-значение. Привязка задает условия сопоставления (x-match: all или any). |
Редко используется, альтернатива Topic, когда ключ маршрутизации неудобно выразить строкой. |
Практические примеры кода (с использованием библиотеки RabbitMQ.Client):
1. Настройка издателя (Publisher) для Direct Exchange:
using var channel = connection.CreateModel();
// Объявляем Direct Exchange
channel.ExchangeDeclare(exchange: "order.direct", type: ExchangeType.Direct, durable: true);
// Сообщение для обработки заказов из ЕС
var message = "Order data...";
var body = Encoding.UTF8.GetBytes(message);
var routingKey = "orders.eu"; // Ключ маршрутизации
// Публикуем сообщение в обменник с указанным routingKey
channel.BasicPublish(exchange: "order.direct",
routingKey: routingKey, // Сообщение пойдет в очередь, привязанную с ключом "orders.eu"
basicProperties: null,
body: body);
2. Настройка потребителя (Consumer) и привязка очереди к Topic Exchange:
using var channel = connection.CreateModel();
// Объявляем Topic Exchange для системы логов
channel.ExchangeDeclare(exchange: "logs.topic", type: ExchangeType.Topic);
// Объявляем временную (exclusive) очередь для этого сервиса
var queueName = channel.QueueDeclare().QueueName;
// Привязываем очередь к обменнику с шаблоном Binding Key.
// Эта очередь получит все логи ошибок (`error`) от любого приложения (`*`).
channel.QueueBind(queue: queueName,
exchange: "logs.topic",
routingKey: "*.error"); // Шаблон Binding Key
// Начинаем потреблять сообщения
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
// В ea.RoutingKey будет, например, "payment.error"
Console.WriteLine($"[ERROR from {ea.RoutingKey}] {message}");
};
channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
Рекомендации по проектированию:
- Именуйте обменники и ключи осмысленно:
notifications.fanout,commands.direct,events.topic. - Используйте
Topicдля гибкой маршрутизации событий. Это самый мощный и часто используемый тип. - Помните о
Fanoutдля широковещания. Он эффективен, когда нужно отправить одно событие многим независимым подписчикам. - Проверяйте существование обменников и очередей перед использованием или используйте флаг
durable: trueдля их сохранения между перезапусками брокера.