Модуль 7

Exchanges: direct, fanout, topic, headers

Ось твій урок у стилі CS50. Вмикай уяву, ми починаємо! 🚀


🎓 Тема: RabbitMQ Exchanges (Direct, Fanout, Topic, Headers)

Привіт, друзі! 👋 Це CS50 (ну, майже 😉).

Сьогодні ми поговоримо про магію, яка відбувається, коли одна частина вашої програми хоче щось сказати іншій, але вони не хочуть "дзвонити" одна одній напряму. Ми розберемося, як керувати потоками повідомлень так, щоб ваш код був гнучким, надійним і готовим до масштабування.

Ми говоримо про Exchanges (обмінники) у брокерах повідомлень (на прикладі RabbitMQ).


1. 🔥 Вступ: Хаос у поштовому відділенні

Уявіть, що ви будуєте систему для великого інтернет-магазину.

Коли користувач натискає "Купити", має статися купа речей: 1. Знятися гроші з картки. 2. Склад має зарезервувати товар. 3. Служба доставки має отримати адресу. 4. Користувачу має прийти PDF-чек на пошту.

Проблема: Якщо ваш сервіс оплати буде по черзі викликати всі ці сервіси (await stock.reserve(), await delivery.create(), await email.send()), то що станеться, якщо сервіс пошти "впаде"? Весь процес покупки зупиниться? Клієнт не зможе купити товар через те, що ми не змогли надіслати PDF? Це катастрофа.

Рішення: Нам потрібен посередник. Сервіс оплати просто кричить у простір: "Гей! Оплата пройшла успішно!" — і йде займатися своїми справами. А кому це цікаво (склад, доставка, пошта) — ті це повідомлення підхоплять.

Ось тут на сцену виходить RabbitMQ, а конкретніше — Exchange.

Риторичне питання: Ви коли-небудь кидали листа в синю поштову скриньку на вулиці? Чи знаєте ви, як саме він потрапляє до вашої бабусі в іншому місті? Вам байдуже, правда? Ви довіряєте пошті.

Exchange — це і є те саме поштове відділення, яке приймає ваші листи і вирішує: "Цей лист у відділ доставки, цей — у смітник, а цей — розксерити і надіслати всім сусідам".

Без розуміння Exchanges ви будете просто пхати повідомлення в одну купу, створюючи "моноліт на милицях". Давайте це виправимо.


2. 🧠 Теоретична база: Як це працює "під капотом"

У RabbitMQ є золоте правило, яке треба викарбувати у пам'яті:

Producer (відправник) ніколи не кладе повідомлення прямо в чергу (Queue). Ніколи.

Замість цього: 1. Producer надсилає повідомлення в Exchange. 2. Exchange дивиться на правила і пересилає повідомлення в одну (або декілька) Queues. 3. Consumer читає з Queue.

Зв'язок між Exchange і Queue називається Binding (прив'язка).

Чотири типи "листонош" (Exchanges):

Ми розглянемо чотири головні стратегії, як Exchange вирішує, куди пхати повідомлення.

  1. Direct (Прямий) — "Снайпер".

    • Логіка: Повідомлення має ключ маршрутизації (Routing Key). Якщо ключ черги точно співпадає з ключем повідомлення — воно туди потрапляє.
    • Аналогія: Ви даєте листа секретарю і кажете: "Віддай це особисто Івану".
  2. Fanout (Віяловий) — "Гучномовець".

    • Логіка: Ігнорує Routing Key. Просто копіює повідомлення у всі черги, які до нього під'єднані.
    • Аналогія: Директор заходить в офіс і кричить: "Сьогодні п'ятниця, можна йти додому раніше!". Це чують усі: і бухгалтери, і розробники.
  3. Topic (Тематичний) — "Розумний фільтр".

    • Логіка: Схоже на Direct, але дозволяє використовувати шаблони (wildcards).
    • Спецсимволи: * (одне слово), # (нуль або більше слів).
    • Аналогія: Ви підписуєтесь на новини. Хтось хоче новини про "Спорт", хтось про "Футбол", а хтось про "Все, що стосується Динамо".
  4. Headers (За заголовками) — "Педант".

    • Логіка: Маршрутизація не за ключем, а за метаданими (headers) повідомлення. Використовується рідко.
    • Аналогія: "Відсортувати листи не за адресою, а ті, де марка червоного кольору і вага понад 100г".

3. 🧪 Приклади: Від простого до реального

Припустимо, ми пишемо систему логування та сповіщень.

Приклад 1: Fanout (Всім-всім-всім)

Ситуація: У нас відбувся реліз нової версії сайту.

  • Exchange: app_updates (type: fanout)
  • Queue A (Mobile Users): підписана на app_updates.
  • Queue B (Email Subscribers): підписана на app_updates.

Код (Python-style pseudo):

channel.exchange_declare(exchange='app_updates', exchange_type='fanout')

# Публікуємо (Routing Key порожній, бо Fanout його ігнорує)
channel.basic_publish(exchange='app_updates', routing_key='', body='New version 2.0 is live!')

Що ви очікуєте? Обидві черги отримають повідомлення. Чому? Бо Fanout працює як бродкаст. Швидко і для всіх.


Приклад 2: Direct (Точкова доставка)

Ситуація: Обробка зображень.

  • Exchange: image_processing (type: direct)
  • Queue A: прив'язана з ключем jpg.
  • Queue B: прив'язана з ключем png.
  • Queue C: прив'язана з ключем jpg (так, можна мати кілька черг на один ключ!).

Код:

# Відправляємо JPG
channel.basic_publish(exchange='image_processing', routing_key='jpg', body='photo.jpg')

Що ви очікуєте? Повідомлення потрапить у Queue A та Queue C. Queue B (для png) буде порожньою. Чому? Повне співпадіння ключа jpg. Це ідеально для розподілу завдань за типом.


Приклад 3: Topic (Вищий пілотаж)

Ситуація: Система логування великої корпорації. Формат ключа: <сервіс>.<рівень_помилки> (наприклад: auth.info, billing.error, billing.warning).

  • Exchange: system_logs (type: topic)
  • Queue "All Errors": хоче бачити помилки будь-якого сервісу. Binding Key: #.error
  • Queue "Billing Monitor": хоче бачити все, що стосується білінгу. Binding Key: billing.*

Дія: Ми посилаємо повідомлення з ключем billing.error.

Питання до вас: У яку чергу потрапить це повідомлення? 1. Тільки в "All Errors"? 2. Тільки в "Billing Monitor"? 3. В обидві?

Відповідь: В обидві! * #.error "з'їдає" будь-який початок, аби в кінці було .error. * billing.* ловить усе, що починається з billing. і має ще одне слово.

А якщо надіслати auth.info? Воно не потрапить нікуди, і просто зникне (якщо черга не налаштована інакше). Жорстоко, але справедливо.


4. 🛠 Практична частина

Час бруднити руки кодом (або хоча б діаграмами). Ось ваші завдання:

  1. 🔹 Hello Fanout: Налаштуйте Exchange типу fanout. Створіть дві тимчасові черги. Надішліть повідомлення. Переконайтеся, що воно впало в обидві. Потім відключіть одну чергу і надішліть знову. Що сталося з повідомленням для відключеної черги?

  2. 🔹 Direct Routing Challenge: Уявіть систему таксі. Є ключі taxi.economy і taxi.business. Створіть чергу, яка слухає ТІЛЬКИ taxi.business. Надішліть замовлення на taxi.economy. Переконайтеся, що бізнес-водії не бачать економ-замовлень.

  3. 🔹 Topic Puzzle (Зірочка vs Решітка): Ключ маршрутизації: ukraine.news.politics. Створіть Binding з ключем ukraine.*. Чи отримає ця черга повідомлення? (Підказка: * замінює рівно одне слово). Виправте Binding так, щоб отримала.

  4. 🔹 Міні-кейс "Розумний Дім": Спроектуйте на папері схему маршрутизації для розумного будинку.

    • Датчики: температура, рух, дим.
    • Кімнати: кухня, вітальня.
    • Завдання:
      • Вмикати світло, якщо рух у будь-якій кімнаті.
      • Вмикати сирену, якщо дим (незалежно від кімнати).
      • Логувати температуру тільки на кухні.
    • Який тип Exchange оберете і які будуть Routing Keys?

5. 💡 Мислення як у розробника

Як відрізнити новачка від профі в роботі з RabbitMQ?

  1. Помилка новачка: Використовувати Default Exchange (надсилати прямо в чергу без вказання exchange) для складної логіки.

    • Профі думає: "Сьогодні мені треба це повідомлення тут, а завтра — ще в трьох місцях. Я зроблю нормальний Exchange одразу, щоб розв'язати руки на майбутнє".
  2. Naming Conventions (Іменування):

    • Не називайте Exchange exchange1. Називайте orders.events або logs.direct.
    • Routing Keys мають бути логічними: domain.entity.action (наприклад, shop.order.created).
  3. Topic vs Fanout:

    • Fanout — найшвидший (CPU не витрачається на перевірку ключів).
    • Topic — найгнучкіший.
    • Профі порада: Якщо сумніваєтесь, беріть Topic. Він може поводитись як Direct (якщо не використовувати * і #) і майже як Fanout (якщо поставити #), але дає можливість змінити логіку без переписування коду продюсера.

6. 🧩 Підсумок

Отже, що ми маємо в сухому залишку:

  • Exchanges — це диспетчери. Вони вирішують долю повідомлень.
  • Direct — для точного попадання.
  • Fanout — для масової розсилки.
  • Topic — для гнучкої фільтрації за шаблоном.
  • Producer не знає про черги, він знає тільки про Exchange.

Тепер ви не просто "відправляєте дані", ви архітектор інформаційних потоків. Ви можете додати нову функціональність (наприклад, сервіс аналітики), просто створивши нову чергу і "підв'язавши" її до існуючого Exchange. І вам не доведеться чіпати код основного сервісу. Це і є Loose Coupling (слабка зв'язність), до якої ми всі прагнемо.

🔜 У наступній серії: Ми надіслали повідомлення, але що як споживач "помер" під час обробки? Як гарантувати, що гроші не зникнуть разом з помилкою сервера? Поговоримо про Acknowledgements (ACKs) та Durability.

А поки що — спробуйте запустити той Fanout приклад. Це було CS50! 🏛️