Асинхронне виконання коду з Django та Celery

10 хв. читання

Коли я починав вчити Django, найважче для мене було виконувати деякий код кілька разів періодично. Я написав чудову функцію, що виконувала потрібну мені дію щодня о 12:00. "Просто", - подумаєте ви. Погано.

Після довгих пошуків, я знайшов чудове рішення - Celery, потужна, асинхронна черга завдань, що використовується для виконання коду у фоні. Але це призвело до додаткових проблем - я не міг знайти жодного туторіалу, про те, як інтегрувати Celery в Django проект.

Звісно ж, зрештою мені вдалось це зробити - про що ця стаття й буде розповідати: "Як інтегрувати Celery в Django проект і додати періодичне виконання певного коду."

В проекті використовується: Python 3.4, Django 1.8.2, Celery 3.1.18 та Redis 3.0.2


Огляд

Для зручності, весь код проекту викладений на github, тож в статті будуть посилання на той код, який необхідно для кожного конкретного кроку.


Що таке Celery

Celery - це асинхронна черга завдань, що базується на розподіленні передачі повідомлень. Вона виконує операції в режимі реального часу, але також підтримує планування завдань. В цій статті ми звернемо увагу на функції планування та періодичного виконання роботи/задачі.

В чому це корисно?

  • Наприклад, вам треба запустити певний код в майбутньому. Можливо вам потрібен доступ до API якогось сайту щогодини. Або вам необхідно надіслати партію листів в кінці кожного дня. Celery зробить планування таких завдань просто й швидко.

  • Якщо довгий процес є частиною процесів вашого додатку, ви можете сміливо використовувати Celery, щоб винести процес у фон, по мірі надходження ресурсів, таким чином, що ваш додаток буде продовжувати реагувати на запити користувачів.

Встановлення

Перед ознайомленням з Celery, завантажте початковий проект з GitHub репозиторія. Далі необхідно активувати віртуальне середовище, встановити залежності (requirements.txt), і створити базу даних(python manage.py migrate). Тоді запустіть сервер і перейдіть за посиланням http://localhost:8000/. Ви повинні побачити наступне "It worked!Congratulations on your first Django-powered page."

Далі давайте встановимо Celery:

pip install celery==3.1.18
pip freeze > requirements.txt

Тепер можна додавати Celery до нашого Django проекту. Це робиться за допомогою трьох простих кроків:

Крок 1: Додаємо celery.py

В теці picha, створіть новий файл celery.py:

from __future__ import absolute_import
import os
from celery import Celery
from django.conf import settings

# встановлюємо стандартні Django налаштування для celery.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'picha.settings')
app = Celery('picha')

# Використвуємо строку для того, щоб воркер не приховав об'єкт при використанні Windows
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)


@app.task(bind=True)
def debug_task(self):
    print('Request: {0!r}'.format(self.request))

Зверніть увагу на коментарі.


Крок 2: Імпорт нового Celery додатка

Щоб переконатись, що Celery додаток завантажується при запуску Django, додайте наступний код в файл __init__.py, який знаходиться в одній теці з файлом settings.py:

from __future__ import absolute_import

# Впевнюємося в тому, що додаток завжди імпортуюється при завантаженні Django і що shared_task використовує його.
from .celery import app as celery_app

Після цього, структура проекту має виглядати так:

├── manage.py
├── picha
│   ├── __init__.py
│   ├── celery.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── requirements.txt

Крок 3: Встановлюємо Redis, в якості Celery "брокера"

Celery використовує "брокери" для того, щоб передавати повідомлення між Django та Celery працівниками. В цьому туторіалі ми будемо використовувати Redis, в якості брокера повідомлень.

Спершу, завантажте Redis з офіційної сторінки та встановіть, або через brew(brew install redis), тоді в терміналі запустіть сервер:

$ redis-server

Щоб перевірити, чи правильно працює Redis, введіть команду

$ redis-cli ping

Повинно вивести PONG.

Після того, як Redis встановлено, додайте наступний код в файл settings.py:

# CELERY STUFF
BROKER_URL = 'redis://localhost:6379'
CELERY_RESULT_BACKEND = 'redis://localhost:6379'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Africa/Nairobi'

Також необхідно додати Redis, як залежність до Django проекту:

$ pip install redis==2.10.3
$ pip freeze > requirements.txt

Це все! Зараз ви можете використовувати Celery в Django. Детальніше про встановлення можна прочитати в офіційній документації Celery.

Перед тим, як рухатись далі, давайте зробимо кілька простих перевірок, щоб впевнитись, що все працює добре.

Перевірка, що Celery готовий до прийому завдань:

$ celery -A picha worker -l info
...
[2015-07-07 14:07:07,398: INFO/MainProcess] Connected to redis://localhost:6379//
[2015-07-07 14:07:07,410: INFO/MainProcess] mingle: searching for neighbors
[2015-07-07 14:07:08,419: INFO/MainProcess] mingle: all alone

Зупиніть процес, натиснувши CTRL+C. Тепер перевіримо, чи готовий планувальник завдань Celery:

$ celery -A picha beat -l info
...
[2015-07-07 14:08:23,054: INFO/MainProcess] beat: Starting...

Супер!

Celery Tasks

Celery використовує завдання, які записуються, як звичайні Python функції.

Для прикладу, перетворимо цю просту функцію в Celery завдання:

def add(x, y):
    return x + y

Спершу додамо декоратор:

from celery.decorators import task

@task(name="sum_two_numbers")
def add(x, y):
    return x + y

Тоді, ви можете виконати це завдання асинхронно за допомогою Celery так:

add.delay(7, 8)

Легко, чи не так?

Таким чином, ці типи завдань чудово підходять для завантаження сторінки не змушуючи користувача чекати завершення виконання завдання у фоновому процесі.

Давайте розглянемо це на більш реальному прикладі.


Завантажте, вище згадану, третю версію коду, який містить додаток feedback, що приймає фідбек від користувача:

├── feedback
│   ├── __init__.py
│   ├── admin.py
│   ├── emails.py
│   ├── forms.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
├── picha
│   ├── __init__.py
│   ├── celery.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── requirements.txt
└── templates
    ├── base.html
    └── feedback
        ├── contact.html
        └── email
            ├── feedback_email_body.txt
            └── feedback_email_subject.txt

Встановіть нові залежності, запустіть сервер, і перейдіть за посиланням http://localhost:8000/feedback/. Ви повинні побачити наступне: celery

Додаємо завдання

Після того, як користувач відправляє форму зворотнього зв'язку, ми хочемо, щоб він продовжував свій шлях, а ми в цей час маємо обробляти відгуки, відправляти e-mail і т.д. у фоновому режимі.

Щоб це реалізувати, спершу в теку 'feedback' додайте новий файл tasks.py:

from celery.decorators import task
from celery.utils.log import get_task_logger

from feedback.emails import send_feedback_email

logger = get_task_logger(__name__)


@task(name="send_feedback_email_task")
def send_feedback_email_task(email, message):
    """sends an email when feedback form is filled successfully"""
    logger.info("Sent feedback email")
    return send_feedback_email(email, message)

Тоді оновіть файл forms.py:

from django import forms
from feedback.tasks import send_feedback_email_task


class FeedbackForm(forms.Form):
    email = forms.EmailField(label="Email Address")
    message = forms.CharField(
        label="Message", widget=forms.Textarea(attrs={'rows': 5}))
    honeypot = forms.CharField(widget=forms.HiddenInput(), required=False)

    def send_email(self):
        # намагаємося обдурити спамерів перевіряючи чи містить дані поле honeypot;
        # не дуже складно/ефективно але працює.
        if self.cleaned_data['honeypot']:
            return False
        send_feedback_email_task.delay(
            self.cleaned_data['email'], self.cleaned_data['message'])

По суті, функція send_feedback_email_task.delay(email, message) обробляє і відправляє e-mail в фоновому режимі в той час, як користувач продовжує використовувати сайт.

Зверніть увагу: в файлі views.py в полі success_url вказано перенаправити користувача за адресою "/", якої ще не існує. Ми виправимо це в наступному розділі._


Періодичні завдання

Часто необхідно запланувати завдання на виконання в певний час - наприклад, запускати певний парсер щодня. Такі завдання легко вирішуються за допомогою Celery і називаються періодичними.

Celery використовує "celery beat", щоб планувати періодичні завдання, який виконує завдання через рівні проміжки часу.

Для прикладу, наступний код планується запускатися кожні 15 хвилин:

from celery.task.schedules import crontab
from celery.decorators import periodic_task


@periodic_task(run_every=(crontab(minute='*/15')), name="some_task", ignore_result=True)
def some_task():
    # do something

Давайте розглянемо більш робочий приклад...


Скачайте четверту версію коду, що містить інший, новий додаток photos, що використовує Flickr API для отримання нових фото, які будуть відображатися на сайті:

├── feedback
│   ├── __init__.py
│   ├── admin.py
│   ├── emails.py
│   ├── forms.py
│   ├── models.py
│   ├── tasks.py
│   ├── tests.py
│   └── views.py
├── manage.py
├── photos
│   ├── __init__.py
│   ├── admin.py
│   ├── models.py
│   ├── settings.py
│   ├── tests.py
│   ├── utils.py
│   └── views.py
├── picha
│   ├── __init__.py
│   ├── celery.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── requirements.txt
└── templates
    ├── base.html
    ├── feedback
    │   ├── contact.html
    │   └── email
    │       ├── feedback_email_body.txt
    │       └── feedback_email_subject.txt
    └── photos
        └── photo_list.html

Встановіть нові залежності, виконайте міграції, запустіть сервер. Спробуйте протестувати нашу форму зворотнього зв'язку знову. Тепер вона повинна перенаправляти на існуючу сторінку.

Що далі?

Ми повинні періодично викликати Flickr API, щоб додати більше фото на наш сайт - це реалізується додаванням Celery завдання.

Додаємо завдання

Додайте файл tasks.py в наш додаток photos:


from celery.task.schedules import crontab
from celery.decorators import periodic_task
from celery.utils.log import get_task_logger

from photos.utils import save_latest_flickr_image

logger = get_task_logger(__name__)


@periodic_task(
    run_every=(crontab(minute='*/15')),
    name="task_save_latest_flickr_image",
    ignore_result=True
)
def task_save_latest_flickr_image():
    """
    Saves latest image from Flickr
    """
    save_latest_flickr_image()
    logger.info("Saved image from Flickr")

Ми виконуємо функцію save_latest_flickr_image() кожні 15 хвилин, обернувши виклик функції в задачі.


Запуск локально

Готові побачити?

Відкрийте два нових термінали, в кожному перейдіть до теки проекту, активуйте віртуальне середовище, і тоді виконайте кожну з цих команд (в різних вікнах):

$ celery -A picha worker -l info
$ celery -A picha beat -l info

Запустіть сервер, і коли ви відкриєте http://127.0.0.1:8000/ , ви повинні побачити одне зображення. Наш додаток буде додавати в фоні ще одне зображення кожні 15 хвилин.

celery

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 6.3K
Приєднався: 7 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація

Читайте також: строки в python, celery, python celery