Ось готовий урок, створений спеціально за твоїм майстер-промптом. Вмикай режим CS50, ми починаємо! 🚀
🎓 Урок: Архітектура Celery та основні компоненти
Привіт, друзі! Радий бачити вас.
Сьогодні ми поговоримо про те, що відрізняє "гальмівний" веб-сайт від блискавичного. Про магію, яка дозволяє робити тисячу справ одночасно, не змушуючи користувача чекати. Ми розберемо Celery.
1. 🔥 Вступ: Чому ваш код "завис"?
Уявіть ситуацію. Ви розробляєте крутий інтернет-магазин. Користувач натискає кнопку "Купити". Ваш код повинен: 1. Зняти гроші з картки. 2. Згенерувати PDF-квитанцію. 3. Відправити email з подякою. 4. Оновити складські залишки.
Якщо ви напишете це в одному звичайному функції-контролері, користувач натисне кнопку і... буде дивитися на кружечок завантаження 10, 20, 30 секунд. Чому? Бо ваш код виконується послідовно. Поки email не піде (а це мережевий запит, він може гальмувати), сторінка не оновиться.
Риторичне питання: Чи буде користувач чекати 30 секунд? Ні, він закриє вкладку і піде до конкурентів.
Тут нам потрібна асинхронність. Але не просто async/await, а щось потужніше. Нам потрібно "скинути" важку роботу комусь іншому, а користувачу миттєво сказати: "Дякую, ми все зробимо!".
🍔 Аналогія: Ресторан швидкого харчування
Коли ви замовляєте бургер у переповненому McDonald’s: 1. Ви платите касиру (це Веб-запит). 2. Касир не біжить смажити котлету сам! Він дає вам чек з номером і кричить "Вільна каса!" наступному. 3. Ваше замовлення з’являється на екрані на кухні (це Черга / Broker). 4. Кухар (це Worker) бачить замовлення, смажить м’ясо і збирає бургер. 5. Коли готово — ваше номер висвічується на табло (це Result Backend).
Celery — це система, яка організовує цю кухню. Без неї ваш касир сам би смажив котлети, поки черга з клієнтів росте аж на вулицю.
2. 🧠 Теоретична база: Як це працює "під капотом"
Celery — це не просто бібліотека, це ціла екосистема. Давайте розберемо її архітектуру.
Головні дійові особи:
- Producer (Клієнт / Ваш веб-додаток): Це той, хто створює задачу. Наприклад, Django або Flask, який каже: "Треба надіслати лист".
- Broker (Брокер повідомлень): (Запам'ятати обов'язково!) Це поштова скринька або "дошка завдань". Сюди Producer кидає задачу. Найпопулярніші брокери — Redis або RabbitMQ. Celery сам не зберігає задачі, йому потрібен цей посередник.
- Worker (Робітник): Це окремий процес (програма), який запущений десь на сервері. Він постійно "стукає" до Брокера: "Є робота? А зараз? А зараз?". Як тільки з'являється задача, Worker хапає її і виконує.
- Result Backend (Сховище результатів): Якщо Worker порахував щось важливе (наприклад, складну формулу), він має кудись покласти результат, щоб Producer міг його забрати. Це може бути база даних або той же Redis.
Як це виглядає логічно:
Клієнт (App) ---> [ БРОКЕР (Redis) ] ---> Worker (Celery)
| |
<--- (перевірка статусу) [ RESULT BACKEND ] <--- (запис результату)
Інтуїтивне розуміння: Клієнт і Воркер ніколи не спілкуються напряму! Вони спілкуються тільки через Брокера. Це дозволяє нам вимкнути Воркера, накопичити 1000 задач у Брокері, потім увімкнути Воркера, і він все розгребе.
3. 🧪 Приклади (від простого до реального)
Припустімо, у нас встановлено 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 slow_hello(name):
print(f"Починаю думати над привітанням для {name}...")
time.sleep(5) # Імітуємо важку роботу (5 секунд)
print(f"Привіт, {name}!")
return "Готово!"
Питання до вас: Якщо я викличу цю функцію просто як slow_hello("Андрій") у Python-консолі, що станеться?
(Пауза на подумати)
Відповідь: Консоль "зависне" на 5 секунд. Це звичайний синхронний виклик.
Приклад 2: Магія .delay()
Щоб віддати задачу Celery, ми робимо так:
# main.py
from tasks import slow_hello
# Викликаємо асинхронно!
result = slow_hello.delay("Андрій")
print("Я не чекав 5 секунд! Я пішов далі.")
print(f"ID задачі: {result.id}")
Що тут відбулося?
1. Python миттєво відправив повідомлення (JSON) у Redis.
2. Метод .delay() повернув нам об'єкт AsyncResult (це як квитанція про замовлення).
3. Програма пішла далі, не чекаючи 5 секунд.
Десь в іншому терміналі має бути запущений Worker:
celery -A tasks worker --loglevel=info
Саме там, у логах воркера, через 5 секунд з'явиться "Привіт, Андрій!".
4. 🛠 Практична частина
Час забруднити руки кодом! Виконайте ці завдання:
🔹 Завдання 1: Запуск середовища
Встановіть Redis (або запустіть через Docker) та бібліотеку:
pip install celery redis
🔹 Завдання 2: "Пошта, яка не гальмує"
Створіть файл email_tasks.py. Напишіть задачу send_email_task(email_address), яка:
1. Приймає email.
2. "Спить" 3 секунди (імітація SMTP з'єднання).
3. Повертає рядок "Email sent to [адреса]".
Створіть скрипт client.py, який у циклі відправляє 10 листів через .delay().
Спостереження: Заміряйте, за скільки часу виконається скрипт client.py. (Має бути миттєво). А потім подивіться, як Worker поступово обробляє ці листи.
🔹 Завдання 3: "Стрес-тест" (А що, якщо...)
- Зупиніть Worker (Ctrl+C).
- Запустіть
client.pyі відправте 5 задач. - Подивіться в Redis (якщо вмієте) або просто усвідомте — задачі не зникли, вони лежать у Брокері.
- Запустіть Worker знову. Питання: Що зробив Worker одразу після запуску? (Він має підхопити пропущені задачі).
🔹 Завдання 4: Отримання результату (Backend)
Додайте налаштування backend='redis://localhost:6379/0' до вашого Celery app.
Створіть задачу, яка додає два числа add(x, y).
У клієнті зробіть так:
task = add.delay(4, 4)
# Чекаємо результат (блокуємо виконання, але тільки для тесту)
print(task.get())
Перевірте, чи повернулося число 8.
5. 💡 Мислення як у розробника
Ви тепер знаєте синтаксис, але як думають профі?
⚠️ Типова помилка новачка №1: Передача об'єктів бази даних
НЕ РОБІТЬ ТАК:
user = User.objects.get(id=1)
send_email.delay(user) # ❌ Погано!
Чому? Поки задача дійде до Воркера, дані користувача в базі можуть змінитись. А ще, серіалізувати цілий об'єкт — це дорого і складно.
ЯК ТРЕБА:
send_email.delay(user_id=1) # ✅ Добре!
Нехай Воркер сам дістане свіжі дані з бази за ID.
⚠️ Типова помилка №2: Задача в задачі
Не змушуйте одну задачу чекати на виконання іншої (task.get() всередині іншої задачі). Це призведе до "deadlock" — всі ваші воркери будуть чекати один одного, і робота зупиниться.
🧠 Порада від досвідченого: Ідемпотентність
Складне слово, проста суть: Якщо задачу виконати двічі, результат має бути той самий. Уявіть, що Воркер впав посеред роботи, а Брокер подумав, що задача не зроблена, і віддав її іншому Воркеру. Ваш код має бути готовим до того, що email можуть спробувати відправити двічі.
6. 🧩 Підсумок
Отже, що ми сьогодні зробили?
1. Зрозуміли, що Broker — це посередник, а Worker — це трудяга.
2. Навчилися відправляти задачі у "фон" за допомогою .delay().
3. Зрозуміли, що веб-сайт не повинен чекати, поки відправиться пошта.
Тепер ви вмієте: Розвантажувати свої веб-додатки та робити їх чуйними (responsive) для користувача.
🍿 Тизер наступного уроку
А що, якщо нам треба відправити звіт керівнику рівно о 9:00 щопонеділка? Ми не будемо натискати кнопку вручну. На наступному уроці ми познайомимося з Celery Beat — будильником для ваших задач.
Це був CS50... тобто, наш урок по Celery. Удачі з кодом! 👋