Что такое Headers Exchange в RabbitMQ?

Ответ

Headers Exchange — это особый тип обменника (exchange) в RabbitMQ, который маршрутизирует сообщения не по ключу маршрутизации (routing key), а на основе заголовков (headers) — пары ключ-значение в свойствах сообщения. Это позволяет реализовать сложную логику маршрутизации, основанную на нескольких атрибутах.

Сравнение с другими типами exchange:

Тип Exchange Основа маршрутизации Пример использования
Direct Точное совпадение routing key Распределение задач по типам (task.image, task.pdf)
Topic Шаблон routing key (*, #) События по иерархии (logs.app.error, user.registered)
Fanout Без маршрутизации (все очереди) Широковещательные уведомления
Headers Совпадение заголовков сообщения Фильтрация по атрибутам (format=json, version=2, priority=high)

Ключевой параметр: x-match

При привязке очереди к Headers Exchange указывается аргумент x-match, который определяет логику сопоставления:

  • x-match = all (по умолчанию): Все пары ключ-значение в аргументах привязки должны присутствовать и совпадать со заголовками сообщения (логическое И).
  • x-match = any: Достаточно совпадения хотя бы одной пары ключ-значение (логическое ИЛИ).

Пример на C# (с использованием библиотеки RabbitMQ.Client):

using RabbitMQ.Client;
using System.Text;
using System.Text.Json;

// 1. Создание соединения и канала
var factory = new ConnectionFactory { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

// 2. Объявление Headers Exchange
channel.ExchangeDeclare(exchange: "notifications_headers", type: ExchangeType.Headers, durable: true);

// 3. Объявление очередей и привязка с разными условиями
// Очередь для JSON-уведомлений высокой важности
channel.QueueDeclare("queue_critical_json", durable: true, exclusive: false, autoDelete: false);
var criticalBindArgs = new Dictionary<string, object>
{
    { "format", "json" },
    { "priority", "high" },
    { "x-match", "all" } // ДОЛЖНЫ быть ОБА заголовка: format=json И priority=high
};
channel.QueueBind(queue: "queue_critical_json", exchange: "notifications_headers", routingKey: "", arguments: criticalBindArgs);

// Очередь для любых XML-уведомлений ИЛИ уведомлений с тегом "legacy"
channel.QueueDeclare("queue_general", durable: true, exclusive: false, autoDelete: false);
var generalBindArgs = new Dictionary<string, object>
{
    { "format", "xml" },
    { "tag", "legacy" },
    { "x-match", "any" } // Достаточно format=xml ИЛИ tag=legacy
};
channel.QueueBind(queue: "queue_general", exchange: "notifications_headers", routingKey: "", arguments: generalBindArgs);

// 4. Публикация сообщения с заголовками
var message = new { Text = "Важное событие!" };
var body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(message));

var properties = channel.CreateBasicProperties();
properties.Persistent = true;
properties.Headers = new Dictionary<string, object>
{
    { "format", "json" },
    { "priority", "high" },
    { "source", "api" }
};

// Сообщение будет доставлено ТОЛЬКО в очередь "queue_critical_json",
// так как совпали ВСЕ требуемые заголовки (format=json, priority=high).
// В "queue_general" оно не попадет, так как нет ни format=xml, ни tag=legacy.
channel.BasicPublish(exchange: "notifications_headers",
                     routingKey: "", // Для Headers Exchange routing key игнорируется или может быть пустым
                     basicProperties: properties,
                     body: body);

Когда использовать Headers Exchange?

  • Сложная фильтрация: Когда для маршрутизации нужны несколько независимых критериев (например, версия API=2, регион=EU, тип клиента=premium).
  • Маршрутизация по атрибутам, а не по теме: Если логика не укладывается в иерархическую структуру Topic Exchange.
  • Динамическая подписка: Клиенты могут гибко настраивать, сообщения с каким набором атрибутов они хотят получать.

Недостаток:

Маршрутизация по заголовкам менее эффективна, чем по routing key, так как требует сопоставления хэш-таблиц. Для высоконагруженных систем с простой логикой Direct или Topic Exchange предпочтительнее.

Ответ 18+ 🔞

Смотри, Headers Exchange в RabbitMQ — это как самый занудный и придирчивый начальник на районе. Он вообще не смотрит на routing key, который все остальные используют. Ему подавай заголовки, эти пары ключ-значение в свойствах сообщения. Хочешь сложную логику, где надо учесть кучу условий сразу? Вот он твой выбор, ёпта.

Чем он не такой, как все:

Тип Exchange На что смотрит Для чего годится
Direct Точное совпадение ключа маршрутизации Разделить задачи: вот тебе картинки (task.image), а вот тебе PDF (task.pdf).
Topic Шаблон ключа (звёздочки, решётки) События по дереву: logs.app.error, user.registered.
Fanout Ни на что не смотрит, шлёт всем Просто орать на всю площадь, чтобы все услышали.
Headers Совпадение заголовков сообщения Вот тут уже интересно: format=json, version=2, priority=high. Фильтрует по атрибутам.

Главная фишка: x-match

Когда привязываешь очередь к такому обменнику, ты должен указать x-match. Это как сказать ему: «Слушай, а как ты будешь сравнивать?»

  • x-match = all (стоит по умолчанию): Все указанные тобой условия должны совпасть. Это логическое «И». Жёстко, зато точно.
  • x-match = any: Достаточно совпадения хотя бы по одному пункту. Логическое «ИЛИ». Попроще, поживее.

Пример на C#, чтобы было понятно, о чём речь:

using RabbitMQ.Client;
using System.Text;
using System.Text.Json;

// Подключаемся, как обычно
var factory = new ConnectionFactory { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

// Объявляем самого придирчивого обменника
channel.ExchangeDeclare(exchange: "notifications_headers", type: ExchangeType.Headers, durable: true);

// Теперь создаём очереди и говорим им, на какие заголовки подписываться

// Очередь для ВАЖНЫХ JSON-уведомлений. Будет оч-чень привередливой.
channel.QueueDeclare("queue_critical_json", durable: true, exclusive: false, autoDelete: false);
var criticalBindArgs = new Dictionary<string, object>
{
    { "format", "json" },
    { "priority", "high" },
    { "x-match", "all" } // Важно! Должны совпасть ОБА условия: И format=json, И priority=high.
};
channel.QueueBind(queue: "queue_critical_json", exchange: "notifications_headers", routingKey: "", arguments: criticalBindArgs);

// Очередь для общих уведомлений. Попроще, жадная до всего.
channel.QueueDeclare("queue_general", durable: true, exclusive: false, autoDelete: false);
var generalBindArgs = new Dictionary<string, object>
{
    { "format", "xml" },
    { "tag", "legacy" },
    { "x-match", "any" } // А тут достаточно ЛЮБОГО из условий: format=xml ИЛИ tag=legacy.
};
channel.QueueBind(queue: "queue_general", exchange: "notifications_headers", routingKey: "", arguments: generalBindArgs);

// А теперь публикуем сообщение
var message = new { Text = "Важное событие!" };
var body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(message));

var properties = channel.CreateBasicProperties();
properties.Persistent = true;
// Вот наши волшебные заголовки
properties.Headers = new Dictionary<string, object>
{
    { "format", "json" },
    { "priority", "high" },
    { "source", "api" }
};

// Что произойдёт?
// Сообщение полетит ТОЛЬКО в "queue_critical_json", потому что у него format=json И priority=high. Оба условия сошлись!
// В "queue_general" оно не попадёт, потому что у него нет ни format=xml, ни tag=legacy. Не повезло.
channel.BasicPublish(exchange: "notifications_headers",
                     routingKey: "", // Ключ маршрутизации тут вообще не важен, можно пустой
                     basicProperties: properties,
                     body: body);

Когда его использовать, а когда нет?

Используй, когда:

  • Логика фильтрации — просто пиздец сложная. Нужно учесть кучу независимых параметров: версия API=2, регион=EU, тип клиента=premium.
  • Маршрутизация идёт по свойствам, а не по теме. Не лезет в иерархию Topic Exchange.
  • Клиенты хотят гибко настраивать подписку. «Дайте мне всё, где format=json И priority=high, но если есть tag=test, то нахуй не надо».

Не используй, когда:

  • Нужна просто скорость. Маршрутизация по заголовкам — это сравнение хэш-таблиц, оно тяжелее, чем просто матчинг по строке ключа. Для простых и быстрых сценариев бери Direct или Topic.
  • Логика простая как три копейки. Не надо тащить сюда слона, если можно обойтись мухой.

Короче, Headers Exchange — это мощный инструмент для сложных случаев, но за его гибкость платишь производительностью. Как говорится, понимаешь, за что боролся.