Ось повноцінний урок, згенерований у стилі Девіда Малана, спеціально для тебе.
🎓 CS50-Style: Асинхронність. Підключення Redis та Celery
Вітаю всіх! Радий бачити вас знову. Сьогодні ми зануримося в одну з тих тем, яка відділяє "просто працюючий код" від професійної, масштабованої системи.
Сьогодні ми говоримо про черги задач. Ми говоримо про Redis та Celery.
1. 🔥 Вступ: Чому ваш сайт "завис"?
Уявіть ситуацію. Ви розробили чудовий інтернет-магазин. Користувач натискає кнопку "Купити". Ваш код має: 1. Зписати гроші з картки. 2. Сформувати PDF-рахунок. 3. Відправити email-підтвердження. 4. Оновити складські залишки.
Якщо все це робити послідовно (синхронно), користувач натискає кнопку і... чекає. Секунду. Дві. П'ять. Кружечок завантаження крутиться. Риторичне питання: Що зробить користувач через 5 секунд очікування? Правильно, він закриє вкладку і піде до конкурентів. Або, що ще гірше, натисне кнопку ще 10 разів, і ви відправите йому 10 листів.
Ми не можемо змушувати користувача чекати, поки наш сервер займається важкою роботою.
Аналогія з рестораном: Уявіть, що ви прийшли в ресторан. Офіціант (ваш веб-сервер) приймає замовлення. * Синхронний підхід (поганий): Офіціант бере замовлення, сам йде на кухню, сам починає смажити котлету, різати салат, наливати напій. Поки він це робить, нові клієнти сидять і чекають. Ніхто не може зробити замовлення, бо офіціант зайнятий готуванням. * Асинхронний підхід (правильний): Офіціант бере замовлення, пише його на папірець, вішає на спеціальну дошку ("тікет") і миттєво повертається до клієнтів. А на кухні є кухарі, які просто беруть тікети з дошки і готують.
Ось цю "дошку з тікетами" і "кухарів" ми сьогодні й налаштуємо. Це і є Redis та Celery.
2. 🧠 Теоретична база: Як це працює "під капотом"
Давайте розберемо це без складних академічних термінів. Нам потрібні два нові компоненти в нашій системі.
1. Broker (Брокер повідомлень) — Це наш Redis
Це та сама "дошка для замовлень" або "поштова скринька". Redis — це супер-швидка база даних, яка працює в оперативній пам'яті. * Його роль: Прийняти задачу від вашого сайту і тримати її, поки хтось її не забере. * Інтуїтивно: Це просто список справ (To-Do list), який лежить у швидкій пам'яті.
2. Worker (Робочий процес) — Це наш Celery
Це "кухар". Це окрема програма (процес), яка запускається паралельно з вашим сайтом. * Його роль: Він постійно "стукає" до Redis: "Гей, є робота? Є робота?". Як тільки з'являється задача, він її хапає і виконує. * Важливо: Celery — це менеджер цих воркерів. Він написаний на Python.
Схема роботи (запам'ятайте цей потік):
- Producer (Сайт/Flask/Django): Каже "Відправ email цьому користувачу". Але не відправляє сам! Він лише створює повідомлення про задачу.
- Broker (Redis): Зберігає повідомлення в черзі:
{"task": "send_email", "args": ["user@example.com"]}. - Consumer (Celery Worker): Бачить нове повідомлення, забирає його і реально відправляє лист.
3. 🧪 Приклади: Від теорії до коду
Для початку, переконайтеся, що у вас встановлені бібліотеки і запущений Redis сервер (наприклад, через Docker або локально).
pip install celery redis
Приклад 1: Мінімальний Hello World
Створимо файл tasks.py. Уявіть, що це наша кухня.
# tasks.py
from celery import Celery
import time
# Налаштовуємо Celery
# Перший аргумент - назва модуля
# broker - адреса нашого Redis (де лежать "тікети")
# backend - куди зберігати результати (теж Redis)
app = Celery('my_tasks',
broker='redis://localhost:6379/0',
backend='redis://localhost:6379/0')
@app.task
def heavy_computation(x, y):
"""
Симулюємо важку задачу.
"""
time.sleep(5) # Уявіть, що тут складні обчислення на 5 секунд
return x + y
Питання до вас: Якщо я запущу цей код як звичайний Python-скрипт, чи виконається функція асинхронно? Відповідь: Ні! Це просто визначення. Нам треба запустити воркера.
Відкрийте термінал і запустіть "кухаря":
celery -A tasks worker --loglevel=info
Ви побачите, як Celery запустився і чекає задач.
Приклад 2: Виклик задачі (Клієнт)
Тепер створимо інший файл main.py (це наш офіціант/сайт).
# main.py
from tasks import heavy_computation
import time
print("1. Клієнт зробив замовлення...")
# Викликаємо задачу через .delay() - це магія Celery!
# Це не блокує виконання коду. Ми миттєво отримуємо ID задачі.
result = heavy_computation.delay(10, 20)
print(f"2. Замовлення прийнято! ID задачі: {result.id}")
print("3. Я можу обслуговувати інших клієнтів, поки кухня готує...")
# Якщо нам ДУЖЕ треба результат прямо зараз (хоча це рідко треба блокуюче):
print(f"4. Результат готовий: {result.get()}") # Тут ми почекаємо 5 сек
Що тут відбулося?
Коли ми викликали .delay(10, 20), Python не чекав 5 секунд. Він миттєво відправив JSON в Redis і пішов далі. А в сусідньому вікні терміналу (де Celery) ви побачите, як задача почала виконуватись.
4. 🛠 Практична частина
Час забруднити руки. Виконайте ці завдання:
🔹 Завдання 1: Запуск середовища
Встановіть Redis (або запустіть у Docker: docker run -d -p 6379:6379 redis) та бібліотеки. Запустіть приклад вище. Переконайтеся, що воркер "бачить" задачу.
🔹 Завдання 2: "Пошта"
Напишіть функцію send_email(email_address), яка виводить у консоль Sending email to..., чекає 3 секунди, а потім виводить Sent!. Викличте її для списку з 5 емейлів у циклі.
Питання: Як швидко відпрацює скрипт-відправник? (Має бути миттєво, менше 0.1 сек).
🔹 Завдання 3: Обробка помилок
Змініть функцію так, щоб якщо y == 0, виникала помилка (ділення на нуль). Запустіть задачу. Подивіться, що пише Celery в логах. Чи "впав" ваш основний скрипт main.py? (Спойлер: Ні, впав тільки воркер на цій задачі).
🔹 Завдання 4: Міні-кейс
Уявіть, що ви робите обробку зображень. Створіть задачу, яка приймає ім'я файлу (рядок), "обробляє" його 2 секунди і повертає рядок "image_processed.jpg". Запустіть 10 таких задач одночасно. Спостерігайте, як Celery їх розгрібає.
🔹 Питання "А що, якщо..."
А що, якщо ми вимкнемо Celery (Ctrl+C в терміналі), але запустимо main.py і відправимо задачі?
Спробуйте.
Потім увімкніть Celery знову.
Результат: Задачі не зникли! Вони чекали в Redis, поки воркер не прокинувся. Це називається надійність (persistence).
5. 💡 Мислення як у розробника
Як думає Senior Developer, працюючи з Celery? Він знає про підводні камені.
-
Не передавайте об'єкти!
- Помилка новачка: Передати в задачу цілий об'єкт користувача з бази даних (
send_email.delay(user_object)). - Проблема: Поки задача дійде до воркера, дані в базі можуть змінитись. А ще об'єкт може не серіалізуватись у JSON.
- Як треба: Передавайте тільки ID (
send_email.delay(user_id)). Воркер сам дістане свіжі дані з бази за ID.
- Помилка новачка: Передати в задачу цілий об'єкт користувача з бази даних (
-
Атомарність транзакцій
- Уявіть: ви зберегли користувача в БД і зразу викликали задачу. Але транзакція БД ще не закрилась (commit не пройшов), а швидкий Celery вже намагається знайти цього юзера в базі.
- Результат:
UserNotFound. - Рішення: Викликати задачі тільки
on_commit(після успішного запису в БД).
-
Ідемпотентність (Страшне слово, проста суть)
- Що, якщо Redis глюкнув і віддав задачу двом воркерам? Або воркер впав посеред роботи, і задачу запустили знову?
- Ваш код має бути готовим до того, що одна й та сама задача виконається двічі. Користувач не має отримати два листи, або списання грошей двічі.
6. 🧩 Підсумок
Отже, що ми сьогодні зробили? Ми розділили нашу програму на дві частини: 1. Голова (Web-додаток), яка швидко роздає команди. 2. Руки (Celery workers), які виконують брудну роботу у фоні.
Ми використали Redis як сполучну ланку.
Тепер ви вмієте: ✅ Робити сайт чутливим і швидким, навіть якщо задачі важкі. ✅ Налаштовувати базову чергу задач. ✅ Розуміти різницю між Producer та Consumer.
Що далі? У наступному "епізоді" ми поговоримо про Celery Beat. Це коли нам треба, щоб задача виконувалася не по кліку користувача, а сама по собі: наприклад, "Щопонеділка о 9:00 надсилати звіт директору".
Це був CS50. Щасти вам з кодом!