Ось готовий урок, створений за твоїм майстер-промптом. Поїхали! 🚀
🎓 CS50 Style: Перша асинхронна задача в Celery
Привіт, друзі! Мене звати [Твоє Ім'я], і сьогодні ми поговоримо про магію, яка відбувається, коли користувач натискає кнопку, а сторінка не зависає.
1. 🔥 Вступ: Чому ми взагалі тут?
Уявіть ситуацію. Ви заходите на сайт, реєструєтесь, натискаєте кнопку "Створити акаунт"... і чекаєте. Крутиться спіннер. Проходить секунда, дві, п'ять... Ви починаєте нервувати. Чи натиснути ще раз? Чи закрити вкладку?
Що відбувається "під капотом"? У цей момент сервер, можливо, генерує важкий PDF-звіт, надсилає вітальний email або обробляє ваше фото для аватарки. І поки він це робить, він ігнорує вас.
Риторичне запитання: Чи хотіли б ви користуватися таким сайтом? Звісно, ні.
Аналогія з життя: Уявіть, що ви в Starbucks. Ви замовляєте лате. Касир (це ваш веб-сервер) приймає замовлення, бере гроші... а потім сам йде молоти каву, збивати молоко, малювати сердечко пінкою. Черга стоїть. Всі чекають, поки касир повернеться. Це — синхронний підхід.
А як це працює насправді? Касир бере замовлення, пише ваше ім'я на стаканчику, передає його баристі (це Celery) і одразу кричить: "Наступний!". Ви відходите, а робота робиться у фоні. Це — асинхронність.
Сьогодні ми навчимо нашого "касира" (Python-код) делегувати важку роботу "баристі" (Celery).
2. 🧠 Теоретична база (Як це працює?)
Отже, щоб реалізувати цю схему "Касир — Бариста", нам потрібні три компоненти. Давайте розберемо їх без складної термінології.
- Producer (Продюсер/Клієнт): Це ваш основний код (наприклад, Django або Flask). Він каже: "Треба надіслати цей лист". Він не надсилає лист, він лише створює задачу.
- Broker (Брокер): Це дошка із замовленнями або "поштова скринька". Сюди потрапляють задачі. Найпопулярніші брокери — Redis або RabbitMQ. Брокер тримає задачі в черзі, поки хтось їх не забере.
- Worker (Воркер/Робітник): Це окремий процес (наш бариста). Він постійно дивиться на Брокера: "Є щось для мене?". Як тільки з'являється задача, він її хапає і виконує.
⚙️ Що відбувається "під капотом"?
Коли ви викликаєте функцію через Celery, ваш Python не виконує її код. Він робить наступне:
1. Бере назву функції та аргументи (наприклад, send_email(user_id=5)).
2. Пакує це в повідомлення (серіалізує, зазвичай у JSON).
3. Кидає це повідомлення в Redis (Брокер).
4. Повертає вам контроль миттєво!
А десь там, в паралельному всесвіті (в іншому процесі), Celery Worker розпаковує повідомлення і запускає функцію.
❗️ Запам'ятайте обов'язково: Основна програма і Celery Worker — це різні процеси. Вони не мають спільної пам'яті. Вони спілкуються тільки через повідомлення (Брокер).
3. 🧪 Приклади: Від простого до реального
Для прикладів уявімо, що у нас вже встановлений Redis і бібліотека Celery (pip install celery redis).
Приклад 1: "Hello World" у світі задач
Створимо файл tasks.py.
from celery import Celery
import time
# Налаштовуємо Celery на роботу з Redis
app = Celery('my_tasks', broker='redis://localhost:6379/0')
@app.task
def add(x, y):
print(f"🧮 Починаю додавати {x} + {y}...")
time.sleep(5) # Імітуємо важку роботу (наприклад, складні обчислення)
result = x + y
print(f"✅ Готово! Результат: {result}")
return result
Питання до вас: Якщо я викличу add(4, 4) у звичайному скрипті, скільки часу я чекатиму?
Правильно, 5 секунд.
А тепер, давайте викличемо це через Celery в іншому файлі run.py:
from tasks import add
# Використовуємо .delay() — це магічна команда запуску в фоні
result = add.delay(4, 4)
print("🚀 Я не чекав 5 секунд! Я пішов далі!")
Чому результат такий?
Тому що add.delay(4, 4) лише кинув задачу в Redis і втік. Якщо у вас не запущений Worker, задача буде просто лежати в Redis і чекати.
Щоб магія спрацювала, у терміналі треба запустити воркера:
celery -A tasks worker --loglevel=info
Приклад 2: Реальний кейс (Надсилання Email)
У реальності ми не додаємо числа, ми робимо I/O операції (мережа, диск).
@app.task
def send_welcome_email(email):
print(f"📧 Підключаюсь до SMTP сервера для {email}...")
time.sleep(3) # Імітація затримки мережі
print(f"📨 Лист відправлено на {email}!")
return True
Уявіть, що це частина реєстрації користувача. Без Celery користувач 3 секунди дивиться на білий екран. З Celery — він миттєво бачить сторінку "Дякуємо за реєстрацію!".
4. 🛠 Практична частина
Час забруднити руки кодом! Виконайте ці завдання:
- 🔹 Hello Celery: Запустіть Redis (можна через Docker), створіть файл
tasks.pyз прикладу вище і запустіть воркера. Переконайтеся, що ви бачите логи у вікні воркера. - 🔹 Ефект "delay": Створіть скрипт, який запускає задачу
add.delay(10, 20), одразу після цього друкує "Задачу відправлено", а потім ще 5 разів друкує крапку.з паузою в 1 секунду. Спостерігайте, як у воркері виконується задача паралельно з вашими крапками. - 🔹 Виправ помилку: Спробуйте передати в задачу аргумент, який неможливо серіалізувати в JSON (наприклад, відкритий файловий об'єкт або складний клас без методів серіалізації). Що напише Celery у логах?
- 🔹 Міні-кейс "Кухня": Напишіть задачу
cook_dish(dish_name, duration), яка приймає назву страви і час приготування. Запустіть цикл, який "замовляє" 3 різні страви підряд. Подивіться, як вони готуються (послідовно чи паралельно? Підказка: за замовчуванням Celery запускає кілька процесів-воркерів, тому може бути паралельно). - 🔹 А що, якщо...: Зупиніть воркера (Ctrl+C). Запустіть задачу через Python. Потім знову увімкніть воркера. Що сталося із задачею? (Вона мала виконатися, бо Redis зберіг її, поки воркер спав).
5. 💡 Мислення як у розробника
Ось тут новачки часто ламають зуби. Послухайте досвідченого "старшого брата":
❌ Помилка №1: Передача об'єктів бази даних
Ніколи, чуєте, НІКОЛИ не передавайте цілий об'єкт користувача в задачу.
# ⛔️ ПОГАНО:
send_email.delay(user_object)
# ✅ ДОБРЕ:
send_email.delay(user_object.id)
Чому? Поки задача дійде до виконання (може пройти хвилина), дані користувача в базі можуть змінитися. А у воркера буде старий, "протухлий" об'єкт. Завжди передавайте ID і діставайте свіжі дані всередині задачі.
❌ Помилка №2: Очікування результату одразу
Якщо ви робите result = task.delay(...) і одразу викликаєте result.get() — ви перетворюєте асинхронний код назад у синхронний! Ви знову заблокували "касира", поки "бариста" не зробить каву.
🧠 Як думає профі?
Профі думає про ідемпотентність. Це страшне слово означає просту річ: чи безпечно виконати цю задачу двічі? Іноді мережа глючить, і Celery може спробувати виконати задачу ще раз. Якщо ваша задача — "списати гроші", і вона виконається двічі... у вас проблеми. Профі пише код так, щоб перевіряти: "А чи не робили ми це вже?".
6. 🧩 Підсумок
Ну що, вітаю! 🎉 Ви щойно розблокували суперсилу фонових задач.
Сьогодні ми:
1. Зрозуміли, що не треба змушувати користувача чекати.
2. Розділили відповідальність: Продюсер (створює) — Брокер (зберігає) — Воркер (робить).
3. Навчилися запускати задачі через .delay().
Тепер ви вмієте: Будувати веб-додатки, які "літають", навіть якщо під капотом вони рахують числа Пі або конвертують відео.
🤔 Що далі? Зараз ми запускали задачі "руками". А що, якщо ми хочемо, щоб задача виконувалася сама щоранку о 9:00 (наприклад, надсилала дайджест новин)? На наступному уроці ми познайомимося з Celery Beat — будильником для ваших задач.
А поки — спробуйте не спалити воркера! Успіхів! 👋