Модуль 22

RabbitMQ + Celery

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


🎓 Урок: RabbitMQ + Celery. Мистецтво фонових задач

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

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

Тема звучить страшно: RabbitMQ та Celery. Але повірте, до кінця цього уроку ви будете дивуватися, як взагалі жили без них раніше.


1. 🔥 Вступ: Чому ваш сайт "зависає"?

Уявіть ситуацію. Ви пишете інтернет-магазин. Користувач натискає кнопку "Купити". Ваша система має: 1. Зняти гроші з картки. 2. Згенерувати PDF-чек. 3. Відправити чек на email. 4. Оновити склад в базі даних.

Генерація PDF і відправка пошти можуть зайняти 5–10 секунд. Що бачить користувач ці 10 секунд? Кружечок завантаження, що крутиться.

А якщо сервіс пошти "впав" і відповідає 30 секунд? Браузер користувача просто покаже помилку "Time out". Клієнт піде, грошей немає.

Риторичне питання: Чи має користувач чекати, поки ваш сервер "сходить" на пошту? Ні! Йому треба миттєво сказати: "Дякуємо, замовлення прийнято!", а все інше зробити потім.

🍔 Аналогія: Ресторан (Restaurant)

Уявіть собі "МакДональдз". * Синхронний підхід (погано): Касир приймає замовлення, сам йде на кухню, смажить котлету, наливає колу, віддає вам пакет і тільки тоді приймає замовлення у наступної людини в черзі. Це катастрофа. * Асинхронний підхід (добре): Касир приймає замовлення, кричить на кухню "Вільна каса!", видає вам чек з номером і одразу обслуговує наступного. А на кухні (у фоні) кухарі готують ваше замовлення.

Ось сьогодні RabbitMQ — це наш "крик на кухню" (система передачі замовлень), а Celery — це кухарі.


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

Давайте розберемо архітектуру. Тут є три головні дійові особи.

1. Producer (Виробник) — Ваше Web-app (Django/Flask/FastAPI)

Коли приходить запит, ваш сайт не робить важку роботу. Він просто створює Повідомлення (Task): "Відправ email користувачу X".

2. Broker (Брокер) — RabbitMQ

Це наша "дошка оголошень" або поштова скринька. * Виробник кидає сюди задачу. * Брокер нічого не обчислює! Він просто зберігає задачу в черзі (Queue), поки її хтось не забере. * Чому RabbitMQ? Він надійний. Якщо світло зникне, він (при правильному налаштуванні) не загубить ваші задачі.

3. Consumer / Worker (Споживач / Воркер) — Celery

Це окремий процес (програма), що працює на сервері паралельно з вашим сайтом. * Він постійно "стукає" до Брокера: "Є робота? Є робота?". * Як тільки з'являється задача, він її хапає, виконує (відправляє той самий email) і звітує про результат.

👉 Що треба запам'ятати залізно: Веб-сайт і Воркер — це різні процеси. Вони можуть навіть жити на різних серверах. Єдиний спосіб їм поспілкуватися — через Брокера (RabbitMQ).


3. 🧪 Приклади: Від теорії до коду

Давайте подивимось, як це виглядає в Python.

Мінімальний приклад

Уявімо, що ми встановили Celery (pip install celery) і запустили RabbitMQ (наприклад, через Docker).

Створюємо файл tasks.py:

from celery import Celery
import time

# Налаштовуємо Celery, вказуючи, де живе наш Брокер (RabbitMQ)
app = Celery('hello', broker='amqp://guest@localhost//')

@app.task
def say_hello(name):
    print(f"Починаю думати над привітанням для {name}...")
    time.sleep(5)  # Імітуємо важку роботу (5 секунд)
    print(f"Привіт, {name}!")
    return "Готово!"

Тепер найцікавіше. Як ми це викликаємо?

Якщо ми в коді напишемо say_hello("Ivan"), це буде звичайна функція. Вона "завісить" програму на 5 секунд.

Очікування студента: То як же змусити її піти в фон? Реальність: Ми використовуємо магічний метод .delay().

# main.py
from tasks import say_hello

print("1. Відправляємо запит...")
result = say_hello.delay("Ivan") 
print("2. Запит відправлено! Йдемо далі.")

Що відбудеться? 1. Python виведе "1. Відправляємо запит...". 2. say_hello.delay миттєво запакує дані {"task": "say_hello", "args": ["Ivan"]} і кине їх у RabbitMQ. 3. Функція поверне управління миттєво, не чекаючи 5 секунд. 4. Виведеться "2. Запит відправлено!".

А десь у сусідньому терміналі, де запущено воркер (celery -A tasks worker), через 5 секунд з'явиться "Привіт, Ivan!".


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

Час забруднити руки кодом! Відкривайте IDE.

Завдання 1: Hello World Встановіть RabbitMQ (або використайте хмарний/Docker). Створіть файл tasks.py з прикладу вище. 1. Запустіть воркер: celery -A tasks worker --loglevel=info. 2. В іншому вікні запустіть Python shell і виконайте say_hello.delay("Студент"). 3. Подивіться в логи воркера. Магія, правда?

Завдання 2: "Важка" математика Напишіть задачу calculate_prime_numbers(limit), яка шукає прості числа. Зробіть так, щоб вона рахувала довго (хоча б 10 секунд). * Викличте її 5 разів підряд через .delay(). * Подивіться, як воркер обробляє їх по черзі (або паралельно, якщо у воркера кілька потоків).

Завдання 3: Імітація помилки (Error Handling) Додайте в задачу умову: якщо name == "Voldemort", викликати raise Exception("He who must not be named!"). * Відправте це ім'я в чергу. * Подивіться, як Celery обробляє помилки. Чи впав сам воркер? (Спойлер: хороший воркер не падає, він просто логує помилку і бере наступну задачу).

Завдання 4: Реальний кейс Напишіть функцію (фейкову), яка "відправляє SMS". Вона має приймати номер телефону і текст. Викличте її.


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

Ось де ми переходимо від новачків до профі. "Я вмію викликати .delay()" — це лише початок.

⛔ Типова помилка новачка #1: Передача об'єктів бази даних

Уявіть, що ви працюєте з Django. НЕ РОБІТЬ ТАК:

user = User.objects.get(id=1)
send_email.delay(user)  # ❌ ПОМИЛКА!

Чому? 1. Поки повідомлення лежить у черзі RabbitMQ, дані користувача в базі можуть змінитись. Воркер отримає стару версію об'єкта user. 2. Серіалізація (перетворення в JSON) складних об'єктів — це біль.

✅ ЯК РОБИТИ ПРАВИЛЬНО: Передавайте тільки ID.

send_email.delay(user_id=1)  # ✅ СУПЕР!

Воркер отримає ID, сам сходить в базу і дістане найактуальнішу версію користувача.

⛔ Типова помилка новачка #2: Очікування результату

Якщо ви робите так:

job = heavy_task.delay()
result = job.get()  # ❌ ЧЕКАЄМО РЕЗУЛЬТАТУ

Ви вбили весь сенс асинхронності! Ваш код знову заблоковано, поки воркер не закінчить. Використовуйте це тільки якщо це абсолютно необхідно, або перевіряйте статус періодично.

🧠 Як думає Senior?

Він думає про Ідемпотентність. Що буде, якщо RabbitMQ випадково віддасть задачу воркеру двічі? Або воркер впаде на половині, і задачу перезапустять? Чи відправиться клієнту два листи? Чи знімуться гроші двічі? Хороша задача написана так, що її можна виконати хоч 10 разів, а результат буде такий самий, як після одного разу.


6. 🧩 Підсумок

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

  1. Синхронність — ворог швидкодії для довгих операцій.
  2. RabbitMQ — це поштар (або черга замовлень).
  3. Celery — це робітник, який розбирає цю чергу.
  4. Ваш сайт лише каже "Зроби це!" (.delay()) і миттєво відповідає користувачу.

Тепер ви вмієте: * Робити так, щоб ваші сайти "літали", навіть якщо під капотом там важка логіка. * Виконувати задачі у фоні.

🔜 Тизер наступного уроку: А що, як нам треба відправляти звіт директору щопонеділка о 9:00 ранку? Ми ж не будемо сидіти і натискати кнопку вручну? На наступному уроці ми розберемо Celery Beat — планувальник задач, який перетворить вашу систему на швейцарський годинник.

А поки — вперед до коду, запускайте своїх кроликів! 🐇