Модуль 22

Celery з RabbitMQ

Ось твій урок у стилі CS50. Вмикай уяву, ніби ми зараз у лекційній залі Гарварду!


🏛️ CS50: Celery та RabbitMQ — Магія фонових задач

Привіт, майбутні інженери! 👋

Сьогодні ми не просто пишемо код. Ми вчимося керувати часом.

1. 🔥 Вступ: Чому ваш сайт "зависає"?

Уявіть ситуацію. Ви заходите на популярний сайт, натискаєте кнопку "Зареєструватися", і... тиша. Коліщатко крутиться 🎡. Секунда, дві, п'ять... Ви починаєте нервувати. Ви натискаєте ще раз. Знову нічого. Ви закриваєте вкладку і йдете до конкурентів.

Що сталося? Сервер просто намагався відправити вам вітальний імейл. Але поштовий сервіс "тупив" 5 секунд. І весь цей час ваш браузер чекав.

Риторичне запитання: Чи має користувач страждати і чекати, поки ваш сервер робить важку роботу? Звісно, ні!

Аналогія: Кав'ярня ☕️

Уявіть Starbucks. 1. Синхронний підхід (Погано): Ви платите касиру. Касир сам йде молоти каву, збивати молоко, малювати серденько на пінці. Черга стоїть і ненавидить вас. 2. Асинхронний підхід (Як треба): Ви платите касиру. Касир кричить: "Латте для Івана!" і дає вам чек. Ви відходите. Бариста (інша людина!) готує каву. Касир вже обслуговує наступного.

Ось тут на сцену виходять наші герої: * Celery — це Бариста (працівник, який робить каву). * RabbitMQ — це черга чеків, які касир клеїть на стійку.


2. 🧠 Теоретична база: Як це працює "під капотом"

Давайте розберемо механіку, але без нудних схем.

У нас є три головні дійові особи:

  1. Producer (Продюсер/Клієнт): Це ваш основний код (наприклад, сайт на Django/Flask). Він каже: "Треба зробити важку задачу".
  2. Broker (Брокер — RabbitMQ): Це поштар 📬. Він бере задачу від Продюсера і кладе її в скриньку (чергу). Він нічого не виконує, він просто зберігає і передає повідомлення.
  3. 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. 🛠 Практична частина

Час забруднити руки! Виконайте ці завдання:

  1. 🔹 Запуск: Запустіть RabbitMQ та Celery worker з прикладу №1. Викличте задачу heavy_computation.delay(10, 20) з консолі Python. Переконайтеся, що воркер "з'їв" задачу.
  2. 🔹 Помилка: Напишіть задачу divide(x, y), яка ділить числа. Викличте її як divide.delay(10, 0). Подивіться в логи воркера. Що він написав? (Він не впаде, але покаже Traceback).
  3. 🔹 Ланцюжок: Спробуйте зробити так: "Порахувати суму" -> "Результат помножити на 2". Підказка: гугліть celery chain або просто викличте другу задачу всередині першої (хоча це анти-патерн, для навчання — ок).
  4. 🔹 Міні-кейс: Уявіть, що ви робите 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 стане нашим ідеальним кеш-сховищем.

А поки — спробуйте не спалити свій процесор у нескінченних циклах! Удачі! 💻