Ось готовий урок, написаний у стилі 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. 🧩 Підсумок
Отже, що ми сьогодні зрозуміли?
- Синхронність — ворог швидкодії для довгих операцій.
- RabbitMQ — це поштар (або черга замовлень).
- Celery — це робітник, який розбирає цю чергу.
- Ваш сайт лише каже "Зроби це!" (
.delay()) і миттєво відповідає користувачу.
Тепер ви вмієте: * Робити так, щоб ваші сайти "літали", навіть якщо під капотом там важка логіка. * Виконувати задачі у фоні.
🔜 Тизер наступного уроку: А що, як нам треба відправляти звіт директору щопонеділка о 9:00 ранку? Ми ж не будемо сидіти і натискати кнопку вручну? На наступному уроці ми розберемо Celery Beat — планувальник задач, який перетворить вашу систему на швейцарський годинник.
А поки — вперед до коду, запускайте своїх кроликів! 🐇