Ось твій урок у стилі CS50. Вмикай уяву, ніби ми зараз у лекційній залі Гарварду!
🏛️ CS50: Celery та RabbitMQ — Магія фонових задач
Привіт, майбутні інженери! 👋
Сьогодні ми не просто пишемо код. Ми вчимося керувати часом.
1. 🔥 Вступ: Чому ваш сайт "зависає"?
Уявіть ситуацію. Ви заходите на популярний сайт, натискаєте кнопку "Зареєструватися", і... тиша. Коліщатко крутиться 🎡. Секунда, дві, п'ять... Ви починаєте нервувати. Ви натискаєте ще раз. Знову нічого. Ви закриваєте вкладку і йдете до конкурентів.
Що сталося? Сервер просто намагався відправити вам вітальний імейл. Але поштовий сервіс "тупив" 5 секунд. І весь цей час ваш браузер чекав.
Риторичне запитання: Чи має користувач страждати і чекати, поки ваш сервер робить важку роботу? Звісно, ні!
Аналогія: Кав'ярня ☕️
Уявіть Starbucks. 1. Синхронний підхід (Погано): Ви платите касиру. Касир сам йде молоти каву, збивати молоко, малювати серденько на пінці. Черга стоїть і ненавидить вас. 2. Асинхронний підхід (Як треба): Ви платите касиру. Касир кричить: "Латте для Івана!" і дає вам чек. Ви відходите. Бариста (інша людина!) готує каву. Касир вже обслуговує наступного.
Ось тут на сцену виходять наші герої: * Celery — це Бариста (працівник, який робить каву). * RabbitMQ — це черга чеків, які касир клеїть на стійку.
2. 🧠 Теоретична база: Як це працює "під капотом"
Давайте розберемо механіку, але без нудних схем.
У нас є три головні дійові особи:
- Producer (Продюсер/Клієнт): Це ваш основний код (наприклад, сайт на Django/Flask). Він каже: "Треба зробити важку задачу".
- Broker (Брокер — RabbitMQ): Це поштар 📬. Він бере задачу від Продюсера і кладе її в скриньку (чергу). Він нічого не виконує, він просто зберігає і передає повідомлення.
- Consumer (Споживач — Celery Worker): Це трудяга 👷. Він постійно дивиться на Брокера: "Є робота? Є робота?". Як тільки з'являється задача, він хапає її і виконує.
🔑 Що треба запам'ятати залізно:
- RabbitMQ — це місце, де лежать задачі. Це просто пам'ять/буфер.
- Celery — це код (Python), який ці задачі виконує.
- Ваш сайт і Celery — це два різні процеси. Якщо сайт впаде, Celery може працювати далі. Якщо Celery впаде, сайт працюватиме, просто листи не підуть.
Що можна розуміти інтуїтивно:
Серіалізація. Коли ви передаєте задачу, Python перетворює функцію та аргументи в текст (JSON/Pickle), щоб RabbitMQ міг це зрозуміти. Celery потім це "розпаковує".
3. 🧪 Приклади: Від Hello World до реальності
Для початку нам треба запустити RabbitMQ. Найпростіше — через Docker (якщо у вас його немає, уявіть, що це магічна чорна скринька, яка вже працює).
# Запускаємо брокера (нашу пошту)
docker run -d -p 5672:5672 rabbitmq
Тепер встановимо Celery:
pip install celery
Приклад 1: Мінімальний (Hello World)
Створіть файл tasks.py.
from celery import Celery
import time
# Налаштовуємо Celery: назва додатку і адреса брокера
app = Celery('hello', broker='amqp://guest@localhost//')
@app.task
def heavy_computation(x, y):
print(f"👷 Починаю рахувати {x} + {y}...")
time.sleep(5) # Симулюємо важку роботу (ніби це обробка відео)
result = x + y
print(f"✅ Готово! Результат: {result}")
return result
Питання до вас: Якщо я просто запущу цей скрипт через python tasks.py, чи спрацює Celery?
Ні! Це просто опис задачі. Нам треба запустити Воркера (Баристу).
Відкрийте термінал і запустіть воркера:
celery -A tasks worker --loglevel=info
Ви побачите купу тексту — це воркер прокинувся і чекає.
Тепер відкрийте інший термінал (це наш Продюсер/Сайт) і запустіть python:
from tasks import heavy_computation
# 1. Запуск "у лоб" (СИНХРОННО)
# heavy_computation(4, 4)
# Це заблокує консоль на 5 секунд. Не робіть так!
# 2. Запуск через Celery (АСИНХРОННО)
res = heavy_computation.delay(4, 4)
print("Я не чекаю! Я пішов далі!")
print(f"ID задачі: {res.id}")
Що відбулося?
Ви миттєво отримали ID задачі. А в першому вікні (де воркер) через 5 секунд з'явиться результат.
Приклад 2: Реальний світ (Надсилання Email з повтором)
У реальному житті мережа може зникнути. Що робити? Celery вміє пробувати знову (Retry).
@app.task(bind=True, max_retries=3) # bind=True дає доступ до self (самої задачі)
def send_email_task(self, email):
try:
print(f"📧 Спроба відправити лист на {email}...")
# Симуляція помилки мережі
import random
if random.choice([True, False]):
raise Exception("Мережа впала!")
print("📨 Лист відправлено!")
except Exception as exc:
print(f"⚠️ Помилка! Пробую ще раз через 2 секунди...")
# Повторити задачу через 2 сек
self.retry(exc=exc, countdown=2)
Чому це круто? Ви не пишете цикли while у своєму коді. Celery сам візьме на себе головний біль з повторами.
4. 🛠 Практична частина
Час забруднити руки! Виконайте ці завдання:
- 🔹 Запуск: Запустіть RabbitMQ та Celery worker з прикладу №1. Викличте задачу
heavy_computation.delay(10, 20)з консолі Python. Переконайтеся, що воркер "з'їв" задачу. - 🔹 Помилка: Напишіть задачу
divide(x, y), яка ділить числа. Викличте її якdivide.delay(10, 0). Подивіться в логи воркера. Що він написав? (Він не впаде, але покаже Traceback). - 🔹 Ланцюжок: Спробуйте зробити так: "Порахувати суму" -> "Результат помножити на 2". Підказка: гугліть
celery chainабо просто викличте другу задачу всередині першої (хоча це анти-патерн, для навчання — ок). - 🔹 Міні-кейс: Уявіть, що ви робите Instagram. Користувач завантажує фото. Напишіть задачу
process_image(image_name), яка:- Пише "Зменшую фото..."
- Чекає 3 секунди.
- Пише "Накладаю фільтр..."
- Чекає 2 секунди.
- Пише "Готово!".
5. 💡 Мислення як у розробника
Як відрізнити новачка від профі при роботі з Celery?
❌ Помилка новачка #1: Передача об'єктів
Новачок передає в задачу цілого користувача з бази даних:
send_email.delay(user_object)
Чому це погано? Поки задача дійде до воркера, дані користувача в базі могли змінитися. А серіалізувати (перетворити в текст) складний об'єкт — це важко і повільно.
✅ Як думає Про:
Передавай тільки ID!
send_email.delay(user_id)
Воркер сам дістане свіжі дані з бази по ID.
❌ Помилка новачка #2: Очікування результату в коді
Новачок пише:
result = task.delay().get()
Чому це погано? .get() блокує програму, поки задача не виконається. Це перетворює асинхронність назад у синхронність! Сенс Celery втрачається.
✅ Як думає Про:
"Fire and forget" (Вистрелив і забув). Якщо треба результат — запиши його в базу даних або відправ повідомлення користувачу через WebSocket, коли все буде готово.
❌ Помилка новачка #3: Атомна бомба в черзі
Якщо задача впаде, Celery може спробувати виконати її знову. Якщо ваша задача — це "Зняти гроші з рахунку", і вона впала після зняття грошей (наприклад, при відправці SMS), повтор задачі зніме гроші ще раз. ✅ Як думає Про: Задачі мають бути Ідемпотентними. Це означає: скільки б разів я не викликав задачу з одними даними, результат має бути таким самим (гроші зняті лише один раз).
6. 🧩 Підсумок
Ну що, видихнули? 😤
Сьогодні ми розібрали потужний інструмент.
* Ви тепер вмієте: Відкладати важкі задачі "на потім", не змушуючи користувача чекати.
* Ви зрозуміли: Що таке Брокер (RabbitMQ) і Воркер (Celery).
* Ви знаєте: Що в чергу краще класти user_id, а не всього юзера.
Це основа масштабованих систем. Facebook, Uber, Netflix — всі вони використовують черги задач.
🚀 Наступного разу: Ми поговоримо про Redis. Зараз ми використовували RabbitMQ як брокера, але що, як нам треба десь швидко зберігати результати виконання задач? Redis стане нашим ідеальним кеш-сховищем.
А поки — спробуйте не спалити свій процесор у нескінченних циклах! Удачі! 💻