Модуль 35

Production-налаштування RabbitMQ

Ось готовий урок, створений спеціально за твоїм запитом, у стилі CS50.


🎓 CS50: Production-налаштування RabbitMQ (Bulletproof Messaging)

Привіт, друзі! Радий вас бачити. 👋

Сьогодні ми не просто "пишемо код". Сьогодні ми рятуємо бізнес.

Минулого разу ми навчилися відправляти повідомлення з точки А в точку Б. Це було весело, як кидати паперові літачки в класі. Але що станеться, якщо вікно відкрите і дме вітер? Або якщо той, хто мав спіймати літачок, раптово зник?

Тема сьогоднішнього уроку — Production-налаштування RabbitMQ. Або, як я люблю це називати: "Як спати спокійно, поки ваші сервери горять".


1. 🔥 Вступ: Чорна п’ятниця і зниклі мільйони

Уявіть ситуацію. Ви працюєте в "Rozetka 2.0". Настала Чорна п'ятниця. Тисячі користувачів натискають "Купити". Ваша система приймає замовлення і кладе їх у чергу RabbitMQ, щоб склад почав пакувати товари.

Все працює ідеально. Ви йдете за кавою... ☕

І раптом — бац! Електрика в дата-центрі зникає на 5 секунд. Сервер з RabbitMQ перезавантажується.

Питання до вас: Коли RabbitMQ підніметься знову, де будуть ті 10,00ш замовлень, які були в черзі, але ще не оброблені складом?

(Пауза)

Якщо ви залишили налаштування "за замовчуванням" (як на dev-середовищі) — вони зникли. Назавжди. Гроші списані, товар не відправлений, клієнти лютують.

Чому без цієї теми не обійтись? Тому що в реальному світі (Production) все ламається. Диски переповнюються, мережа відвалюється, процеси "падають". Ваша задача як інженера — зробити так, щоб система пережила ці падіння без втрати даних.

Аналогія: Dev-налаштування RabbitMQ — це як передавати записку другу в руки. Якщо друг відволікся, записка впала. Prod-налаштування — це рекомендований лист із описом вкладення. Пошта (RabbitMQ) зобов'язана зберегти його в сейфі, навіть якщо будівля пошти зачиниться на обід, і віддати його тільки під підпис.


2. 🧠 Теоретична база: Три кити надійності

Щоб наш RabbitMQ став "броньованим", нам треба зрозуміти три концепції. Не зубріть їх, зрозумійте логіку.

1. Durability (Довговічність черги) та Persistence (Стійкість повідомлень)

Це різні речі! Багато новачків тут плутаються.

  • Durable Queue: Це означає, що сама черга (її ім'я та налаштування) збережеться після перезавантаження RabbitMQ. Але не вміст! Це як порожній сейф: сейф стоїть, але всередині пусто.
  • Persistent Message: Це означає, що повідомлення записується на жорсткий диск, а не висить у оперативній пам'яті.

Запам'ятайте: Щоб дані вижили після рестарту, треба І Durable Queue, І Persistent Messages.

2. Acknowledgements (Підтвердження, або "Ack")

Уявіть, що ви дали задачу воркеру (споживачу): "Оброби замовлення №5". Воркер взяв його... і через мілісекунду його процес "впав" (OOM kill, помилка в коді).

Якщо RabbitMQ думає: "Ну, я віддав задачу, моя робота зроблена" — замовлення втрачено. Тому в Production ми використовуємо Manual Acks. RabbitMQ чекає, поки воркер скаже: "Я закінчив, все ок, можеш видаляти". Тільки тоді повідомлення зникає.

3. High Availability (Кластеризація та Quorum Queues)

Що, якщо згорів сам сервер, де стоїть RabbitMQ? Диск мертвий. Тут на сцену виходять Quorum Queues (Кворумні черги). Це сучасний стандарт. Дані копіюються на 3 або 5 нод (серверів). Якщо один помирає, інші обирають нового лідера і продовжують роботу. Це алгоритм консенсусу Raft (схоже на те, як працює Kubernetes або CockroachDB).


3. 🧪 Приклади (Python/Pika)

Давайте подивимось на код.

Крок 1. "Наївний" підхід (Як ми робили раніше)

# ❌ НЕ РОБІТЬ ТАК У PROD
channel.queue_declare(queue='hello') # За замовчуванням non-durable
channel.basic_publish(exchange='', routing_key='hello', body='Важливе замовлення!')

Що ви очікуєте? Повідомлення летить швидко. Реальність: sudo service rabbitmq-server restart. Пуф! Черги немає, повідомлення немає.


Крок 2. Production-style Publisher (Відправник)

Ми робимо чергу стійкою і повідомлення "персистентним".

import pika

# Підключаємось...
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# ✅ 1. Оголошуємо чергу як DURABLE
# Якщо RabbitMQ перезавантажиться, черга залишиться існувати.
channel.queue_declare(queue='task_queue', durable=True)

message = "Замовлення на $1,000,000"

channel.basic_publish(
    exchange='',
    routing_key='task_queue',
    body=message,
    properties=pika.BasicProperties(
        # ✅ 2. Робимо повідомлення PERSISTENT (delivery_mode=2)
        # RabbitMQ запише його на диск.
        delivery_mode=pika.DeliveryMode.Persistent
    ))

print(f" [x] Sent {message}")
connection.close()

Крок 3. Production-style Consumer (Воркер)

Тут ми додаємо ручне підтвердження (ack) і налаштування QoS (prefetch_count).

def callback(ch, method, properties, body):
    print(f" [x] Отримав {body.decode()}")

    # ... тут іде важка обробка (запис в БД, відправка пошти) ...
    # Уявіть, що тут сталась помилка. Якщо ми не відправимо ack, 
    # RabbitMQ поверне повідомлення в чергу іншому воркеру!

    print(" [x] Готово")

    # ✅ 3. Ручний ACK
    # Тільки ТЕПЕР RabbitMQ видалить повідомлення.
    ch.basic_ack(delivery_tag=method.delivery_tag)

# ✅ 4. QoS / Prefetch Count
# Не давай мені більше 1 задачі за раз! 
# Не завалюй мене роботою, поки я не підтвердив попередню.
channel.basic_qos(prefetch_count=1)

channel.basic_consume(queue='task_queue', on_message_callback=callback)
channel.start_consuming()

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

Запустіть свій RabbitMQ (локально або в Docker). Час бруднити руки!

Завдання 1: "Test Crash"

  1. Запустіть скрипт Publisher з кодом вище (відправте повідомлення).
  2. Не запускайте Consumer.
  3. Перезавантажте RabbitMQ (або Docker контейнер).
  4. Зайдіть в RabbitMQ Management Interface (http://localhost:15672).
  5. Питання: Чи бачите ви там чергу task_queue? Чи є в ній 1 повідомлення? (Має бути!)

Завдання 2: "Лінивий воркер"

  1. Модифікуйте код Consumer-а. Закоментуйте рядок ch.basic_ack(...).
  2. Запустіть Consumer. Він отримає повідомлення.
  3. Зупиніть Consumer (Ctrl+C).
  4. Подивіться в адмінку RabbitMQ. Повідомлення зникло чи повернулося в статус Ready?
  5. Поясніть, чому це сталося.

Завдання 3: QoS Challenge

  1. Зробіть Consumer, який "спить" (time.sleep(5)) при обробці.
  2. Запустіть 2 екземпляри цього Consumer-а.
  3. Швидко відправте 10 повідомлень.
  4. Якщо prefetch_count=1, як розподіляться задачі? А якщо прибрати цей рядок? Перевірте на практиці.

Завдання 4: Міні-кейс

Ви розробляєте систему генерації PDF-звітів. Генерація одного звіту займає 30 секунд. Користувачі скаржаться, що іноді звіти не приходять на пошту. Напишіть, які саме налаштування RabbitMQ ви перевірите в першу чергу, щоб виправити це?


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

Як відрізнити новачка від профі в чергах повідомлень?

Помилка новачка: Використовує auto_ack=True (автоматичне підтвердження) для фінансових транзакцій. Результат: Сервер отримав задачу, автоматично сказав "Ок", почав обробляти, впав з помилкою. Гроші зникли.

Як думає Senior: 1. "At least once" delivery. Я розумію, що через збої мережі RabbitMQ може доставити одне й те саме повідомлення двічі (наприклад, воркер зробив роботу, але ack не дійшов до брокера через розрив зв'язку). 2. Тому мій код воркера має бути Ідемпотентним. Це означає: Якщо я оброблю те саме замовлення вдруге, я не спишу гроші ще раз. Я перевірю в БД по ID: "О, це замовлення вже оплачене", і просто зроблю ack.

Порада з практики: Ніколи не використовуйте дефолтний Exchange і дефолтні налаштування пам'яті в Prod. Налаштуйте High Watermark (ліміт пам'яті), щоб RabbitMQ не "вбив" сам себе, з'ївши всю RAM сервера.


6. 🧩 Підсумок

Отже, що ми сьогодні додали до нашого арсеналу?

  1. Durable Queues & Persistent Messages — щоб пережити рестарт сервера.
  2. Manual Acks — щоб не губити задачі, якщо воркер впав.
  3. Prefetch Count — щоб рівномірно розподіляти навантаження (Fair Dispatch).

Тепер ви можете висмикнути шнур живлення з сервера, вставити назад, і ваша бізнес-логіка продовжить працювати, ніби нічого не сталося. Це і є Production-рівень.

🔜 В наступній серії: Ми навчилися не губити повідомлення. Але що робити, якщо повідомлення "отруйне" (викликає помилку щоразу)? Воно буде крутитися в циклі вічно, вбиваючи ваших воркерів? Наступного разу поговоримо про Dead Letter Exchanges (DLX) — кладовище для поганих повідомлень.

А поки що — це був CS50. Удачі з кодом! 🚀