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

Ответ

Topic Exchange — это тип точки обмена (exchange) в RabbitMQ, который маршрутизирует сообщения в очереди на основе совпадения шаблона (pattern matching) ключа маршрутизации (routing key) сообщения и шаблона привязки (binding key) очереди.

Ключевые особенности:

  • Гибкая маршрутизация: Позволяет реализовать сложные сценарии, такие как подписка на категории событий (например, логи определённого уровня и источника).
  • Специальные символы в binding key:
    • * (звездочка) — заменяет ровно одно слово в ключе.
    • # (решетка) — заменяет ноль или более слов.

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

// 1. Объявление exchange типа 'topic'
channel.ExchangeDeclare(exchange: "logs_topic", type: ExchangeType.Topic);

// 2. Привязка очередей с разными шаблонами
// Эта очередь получит все ошибки любого приложения
channel.QueueBind(queue: "all_errors",
                  exchange: "logs_topic",
                  routingKey: "*.error");

// Эта очередь получит все логи от приложения 'app1'
channel.QueueBind(queue: "app1_logs",
                  exchange: "logs_topic",
                  routingKey: "app1.*");

// Эта очередь получит всё, что связано с платежами и ошибками
channel.QueueBind(queue: "critical_payment_errors",
                  exchange: "logs_topic",
                  routingKey: "payment.#.error");

// 3. Публикация сообщений с разными routing keys
// Попадёт в очереди: 'all_errors' и 'app1_logs'
var body1 = Encoding.UTF8.GetBytes("App1 error log");
channel.BasicPublish(exchange: "logs_topic",
                     routingKey: "app1.error",
                     basicProperties: null,
                     body: body1);

// Попадёт ТОЛЬКО в очередь: 'app1_logs'
var body2 = Encoding.UTF8.GetBytes("App1 info log");
channel.BasicPublish(exchange: "logs_topic",
                     routingKey: "app1.info",
                     basicProperties: null,
                     body: body2);

Практическое применение: Создание гибких систем событий (event-driven architecture), где потребители могут динамически подписываться на интересующие их категории сообщений, например, для распределённого логирования, нотификаций или обновления кэша.

Ответ 18+ 🔞

Так, Topic Exchange в RabbitMQ, давайте разберем эту штуку без соплей.

Представьте себе почтовое отделение, но не обычное, а такое, где работник — абсолютный долбоёб, но зато у него есть магическая способность читать шаблоны на конвертах. Это и есть Topic Exchange. Его задача — не просто кидать письма в ящик по имени, а сортировать их по каким-то признакам, которые ты сам придумал.

Суть в чём: ты публикуешь сообщение с routing key — это типа адрес, но адрес составной, словами. Например, "app1.payment.error" или "user.profile.updated". А очереди привязываются к этому exchange не по точному имени, а по шаблону (binding key). И тут начинается магия двух спецсимволов, которые надо запомнить, как отче наш:

  • * (звездочка) — заменяет ровно одно слово. Не ноль, не два, а ровно ОДНО. Тупая затычка.
  • # (решётка) — заменяет ноль или сколько угодно слов. Это уже прожорливая хрень, которая схавает всё.

Вот смотри на примере, и всё станет ясно. Допустим, мы делаем систему логирования. Код на C# (библиотека RabbitMQ.Client).

// 1. Создаём сам exchange типа 'topic'. Назовём его, скажем, 'logs_topic'.
channel.ExchangeDeclare(exchange: "logs_topic", type: ExchangeType.Topic);

// 2. Привязываем очереди с разными шаблонами. Это как настраиваем правила для нашего долбоёба-сортировщика.

// Эта очередь хочет ВСЕ ошибки от ЛЮБОГО приложения.
// Шаблон "*.error" значит: первое слово — любое, второе — обязательно "error".
channel.QueueBind(queue: "all_errors",
                  exchange: "logs_topic",
                  routingKey: "*.error");

// Эта очередь хочет ВСЁ, что угодно, от приложения 'app1'.
// Шаблон "app1.*" значит: первое слово "app1", а второе — какое угодно.
channel.QueueBind(queue: "app1_logs",
                  exchange: "logs_topic",
                  routingKey: "app1.*");

// А эта — вообще хитрая жопа. Хочет всё про платежи, что кончается на ошибку.
// Шаблон "payment.#.error": начинается с "payment", потом сколько угодно слов (или ноль), и в конце — "error".
channel.QueueBind(queue: "critical_payment_errors",
                  exchange: "logs_topic",
                  routingKey: "payment.#.error");

// 3. А теперь публикуем сообщения и смотрим, куда что полетит.

// Сообщение с ключом "app1.error".
// Куда попадёт? Давай думай.
// "*.error" — подходит? Да, первое слово "app1" (любое), второе "error". В очередь 'all_errors' летит.
// "app1.*" — подходит? Абсолютно. В 'app1_logs' тоже летит.
// "payment.#.error" — нет, не начинается с "payment". Значит, в третью очередь — мимо.
var body1 = Encoding.UTF8.GetBytes("App1 error log");
channel.BasicPublish(exchange: "logs_topic",
                     routingKey: "app1.error",
                     basicProperties: null,
                     body: body1);

// Сообщение с ключом "app1.info".
// "*.error" — не подходит, в конце не "error". В 'all_errors' НЕ летит.
// "app1.*" — подходит идеально. В 'app1_logs' — пожалуйста.
// "payment.#.error" — опять мимо.
var body2 = Encoding.UTF8.GetBytes("App1 info log");
channel.BasicPublish(exchange: "logs_topic",
                     routingKey: "app1.info",
                     basicProperties: null,
                     body: body2);

// Допустим, отправим "payment.gateway.api.critical.error".
// Первая очередь ("*.error")? Нет, первое слово "payment", а потом не одно слово, а целая куча. Звездочка '*' на такое не покупается. Мимо.
// Вторая ("app1.*")? Тоже нет.
// А вот третья — "payment.#.error" — О, ДА! Начинается с "payment", потом сколько-то слов, и в конце "error". Идеальное попадание. Сообщение улетит только в 'critical_payment_errors'.

Короче, вся мощь этого exchange — в этой гибкой, ебаной, шаблонной маршрутизации. Идеально, когда у тебя куча сервисов и им нужно подписываться не на все события подряд, а выборочно: на ошибки, на события от конкретного модуля, на определённые категории действий. Система нотификаций, логирования, обновления кэша — везде, где нужна такая избирательная рассылка. Главное — шаблоны правильно составить, а то получишь сообщение не там, где ждал, и будешь потом ебучку ломать.