Что произойдет при публикации сообщения в несуществующий exchange в RabbitMQ?

Ответ

Поведение зависит от флага mandatory, который указывает, что делать с сообщением, если его невозможно доставить в очередь.

1. mandatory: false (значение по умолчанию) Сообщение будет безвозвратно отброшено брокером. Отправитель не получит никакого уведомления об ошибке. Это поведение называется "fire and forget" и подходит для сценариев, где потеря отдельных сообщений допустима.

2. mandatory: true Если exchange не существует, брокер вернет сообщение отправителю через механизм Basic.Return. Отправитель должен быть готов обработать это событие.

Практический пример с обработкой возврата:

using RabbitMQ.Client;
using System.Text;

var factory = new ConnectionFactory() { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

// 1. Подписываемся на событие возврата сообщения
channel.BasicReturn += (sender, args) =>
{
    var body = args.Body.ToArray();
    var message = Encoding.UTF8.GetString(body);
    Console.WriteLine($"[RETURN] Сообщение возвращено!");
    Console.WriteLine($"  Причина: {args.ReplyText}");
    Console.WriteLine($"  Exchange: {args.Exchange}");
    Console.WriteLine($"  Routing Key: {args.RoutingKey}");
    Console.WriteLine($"  Тело: {message}");
};

// 2. Публикуем сообщение в несуществующий exchange с mandatory=true
var body = Encoding.UTF8.GetBytes("Test Message");
var properties = channel.CreateBasicProperties();
properties.Persistent = true; // Делаем сообщение устойчивым

channel.BasicPublish(
    exchange: "non_existent_exchange", // Несуществующий exchange
    routingKey: "some.routing.key",
    mandatory: true, // Ключевой параметр
    basicProperties: properties,
    body: body);

Console.WriteLine("Сообщение опубликовано. Ожидайте Basic.Return...");

Вывод в консоли будет примерно таким:

Сообщение опубликовано. Ожидайте Basic.Return...
[RETURN] Сообщение возвращено!
  Причина: NO_ROUTE
  Exchange: non_existent_exchange
  Routing Key: some.routing.key
  Тело: Test Message

Рекомендации:

  • Всегда используйте mandatory: true в системах, где критична гарантированная доставка или необходима мгновенная реакция на ошибки маршрутизации.
  • Для повышения надежности комбинируйте это с подтверждениями публикации (Publisher Confirms) и устойчивыми сообщениями (Persistent = true).
  • В продакшене обязательно реализуйте логику повторной отправки или перемещения "отравленных" сообщений в Dead Letter Exchange (DLX).

Ответ 18+ 🔞

Давай разберем эту штуку с флагом mandatory, а то народ часто путается, как оно работает на самом деле. Представь, что ты шлёшь посылку через какую-то контору.

1. mandatory: false (дефолтное состояние, все так живут) Это как отправить посылку и тут же забыть. Ты кинул её в ящик этой конторы и пошёл по своим делам. Если адресат (очередь) не найден или сам пункт приёма (exchange) сгорел — ну и хуй с ним. Посылка тупо пропадёт, а тебе никто даже не позвонит, чтобы сказать "чувак, у нас тут проблемка". Подходит, когда потеря пары сообщений — не конец света. Типа "отправил и забыл", fire and forget, как говорят умные дядьки.

2. mandatory: true (включил режим параноика) А вот это уже серьёзнее. Ты говоришь брокеру: "Слушай, пацан, если не сможешь доставить эту хуйню куда надо — немедленно верни её мне обратно, я разберусь!". Если exchange не существует или для сообщения нет подходящего маршрута, брокер не будет молча его выбрасывать. Вместо этого он вернёт тебе сообщение через специальный механизм — Basic.Return. Но чтобы его получить, ты должен заранее подписаться на эту фигню, иначе возврат улетит в никуда.

Пример на пальцах, как это выглядит в коде:

using RabbitMQ.Client;
using System.Text;

var factory = new ConnectionFactory() { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

// 1. Вешаем слушателя на возвраты. Без этого — нихуя не получишь!
channel.BasicReturn += (sender, args) =>
{
    var body = args.Body.ToArray();
    var message = Encoding.UTF8.GetString(body);
    Console.WriteLine($"[ОЙ, ВСЁ!] Сообщение вернулось, как бумеранг!");
    Console.WriteLine($"  Причина провала: {args.ReplyText}");
    Console.WriteLine($"  Exchange, который не нашёлся: {args.Exchange}");
    Console.WriteLine($"  Ключ маршрутизации: {args.RoutingKey}");
    Console.WriteLine($"  Само сообщение: {message}");
};

// 2. Пытаемся отправить сообщение в несуществующий exchange. Ну, чисто проверить систему.
var body = Encoding.UTF8.GetBytes("Тестовое сообщение, приём!");
var properties = channel.CreateBasicProperties();
properties.Persistent = true; // Делаем сообщение живучим, чтобы пережило перезагрузку

// Вот тут главный момент — mandatory: true
channel.BasicPublish(
    exchange: "несуществующий_обменник", // Очевидно, его нет
    routingKey: "какойто.ключ",
    mandatory: true, // Включаем режим "верни, если что"
    basicProperties: properties,
    body: body);

Console.WriteLine("Сообщение якобы отправлено. Ждём-с...");

И что мы увидим в консоли, когда всё накроется медным тазом:

Сообщение якобы отправлено. Ждём-с...
[ОЙ, ВСЁ!] Сообщение вернулось, как бумеранг!
  Причина провала: NO_ROUTE
  Exchange, который не нашёлся: несуществующий_обменник
  Ключ маршрутизации: какойто.ключ
  Само сообщение: Тестовое сообщение, приём!

Итог и советы на жизнь:

  • Ставь mandatory: true в системах, где каждая потерянная посылка — это пиздец, скандал и разбор полётов. Чтобы сразу знать, если маршрутизация сломалась.
  • Но не обольщайся — это только про маршрутизацию. Чтобы быть совсем спокойным, комбинируй это с подтверждениями от брокера (Publisher Confirms), что он вообще принял сообщение, и ставь Persistent = true.
  • В реальном продакшене обязательно придумай, куда девать эти возвращённые сообщения. Обычно их пихают в Dead Letter Exchange (DLX) — специальную помойку для неудачников, чтобы потом разобраться.