5 сентября 2014 г.

Основы Celery для Django

При создании некоторых web-приложений порой требуется выполнять задачи асинхронно запросу пользователя или периодически.

К первым можно, например, отнести отправку уведомлений по электронной почте. В этом случае невозможно гарантировать быструю обработку запроса. Поэтому отправку уведомления можно поместить в параллельный поток и запрос не будет зависеть от скорости этой самой отправки.

Ко вторым относятся задачи, обычно размещаемые в cron.

И здесь на помощь как раз может придти система типа Celery — асинхронная очередь задач, основанная на передаче сообщений. Надо сказать, что Celery можно и нужно использовать не только в web-приложениях. Но здесь я расскажу о ней применительно к популярному web-фреймверку Django. Еще стоит оговориться, что я раскрою лишь малую часть системы Celery (достаточную для использования в Django), потому как её возможности достаточно велики.

Для работы Celery необходимо выбрать брокер — систему, которая будет заниматься очередью сообщений — хранением и выборкой. Celery поддерживает множество брокеров. Среди которых есть и Redis. Именно Redis и RabbitMQ заявлены как стабильные для Celery. Redis — очень популярная система управления структурами данных на основе key-value. Будем считать, что Redis установлен и настроен.

Установка самого Celery совершенно не вызывает никаких проблем и описана в руководстве. После установки Celery уже можно использовать. В том числе и для Django. Но управление задачами будет не столь удобно, как с использованием пакета django-celery для фреймверка. Этот пакет позволит использовать админку Джанги для управления периодическими задачами, автоопределение задач из модулей фреймверка. Есть и другие пока не интересные для нас фичи.

Итак, после установки этих двух пакетов — давайте настроим их. Вообще, Celery можно настроить тысячей разных способов. Я покажу лишь один из вариантов. Вы можете выбрать свой. В файле settings.py вашего django-проекта добавьте следующее:

import djcelery
# указываем на то, что расписание будет задаваться посредством django-ORM
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'
# указываем брокер сообщений
BROKER_URL = 'redis://127.0.0.1:6379/0'
# указываем хранилище результатов (можете не указывать)
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'
# формат хранения задач (можете не указывать)
CELERY_TASK_SERIALIZER = 'json'
# формат хранения результатов (можете не указывать)
CELERY_RESULT_SERIALIZER = 'json'
# если настроены джанговские параметры уведомлений по почте
# и данный параметр True, то исключения в задачах будут
# фиксироваться на почте администраторов приложения
CELERY_SEND_TASK_ERROR_EMAILS = True
# инициализация django-celery
djcelery.setup_loader()

# добавляем приложение django-celery и не забываем после этого сделать миграции
INSTALLED_APPS = (
    # ...
    'djcelery',
    # ...
)

Основные установки для интеграции Django и Celery заданы. Теперь создадим приложение Celery (в файле celery.py рядом с settings.py) почти как описано в документации:

from __future__ import absolute_import
import os

from celery import Celery
from django.conf import settings

# устанавливаем settings модуль для приложения celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings')

# создаем celery-приложение
app = Celery('name-of-app')

# используем установки django-проекта в celery-приложении
app.config_from_object('django.conf:settings')

# автоопределение задач из django-проекта
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

Всё. Можно создать задачи и запускать manage.py celeryd * и manage.py celerybeat для тестирования интеграции. Но мы пойдем немного дальше и я покажу как запустить celery демоном в Ubuntu для service. Создайте /etc/init.d/celeryd и запишите в него код отсюда. Затем в /etc/default/celeryd впишите следующее:

# Название узла демона
CELERYD_NODES="projectname"

# Исполняющий файл
CELERY_BIN="/usr/local/bin/celery"

# Указание celery-приложения
CELERY_APP="proj.celery:app"

CELERYD_CHDIR="/путь/до/джанго/проекта/"

# параметры запуска демона
# опция -B запускает демон с celerybeat-ом
CELERYD_OPTS="--time-limit=300 --concurrency=3 -B"

# лог-файл демона. %N - название узла
CELERYD_LOG_FILE="/var/log/celery/%N.log"

# Идентификатор процесса демона. %N - название узла
CELERYD_PID_FILE="/var/run/celery/%N.pid"

# Группа и пользователь, от имени которого будет работать демон
# у меня тот же что и django
CELERYD_USER="www-data"
CELERYD_GROUP="www-data"

# Если директории лога и pid-а отсутствуют - они создадутся автоматом
CELERY_CREATE_DIRS=1

Это все премудрости простого способа развернуть Celery, интегрированную с Django. Теперь достаточно выполнить

sudo service celeryd start

и читать логи celery — они подскажут, если что-то пошло не так.

Теперь рассмотрим практическое применение celery в django-проекте.

Для создания асинхронной задачи в любом django-приложении создайте файл tasks.py:

# -*- coding: utf8 -*-
import datetime
from celery import shared_task
@shared_task
def test_task():
    print '{0}'.format(datetime.datetime.now())

Теперь в админке /admin/djcelery/periodictask/add/ можно добавить эту задачу для выполнения с любой периодичностью, ограниченной лишь celerybeat.

Если же нужно создать асинхронную задачу, то можно определить любую функцию или метод как:

from celery import task
@task
def func():
    print 'call task func'

а затем вызвать её где-то в приложении, например так:

func.delay()

Замечу, что процессы описаны для Django 1.5-1.7, Celery 3.1.13, django-celery 3.1.10. Если у вас будут другие версии пакетов, то что-то может не заработать. Документация — в помощь.

Полезные ссылки:

UPDATE: Метод manage.py celeryd объявлен устаревшим, поэтому вместо него следует использовать manage.py celery worker

2 комментария:

Антон комментирует...

Спасибо за статью, в частности, за способ запуска celery как демона в Ubuntu.
Как можно решить вопрос graceful restart'а демона при очередном релизе django-проекта?

Иван комментирует...

Антон, рискну предположить, что если Celery очень часто вызывает асинхронные задачи из запросов и потеря данных критична или продолжительность выполнения задач большая, то это тема отдельного поста. Я же в этот вопрос пока не погружался. В документации и инетах пока на него не натыкался.

Отправить комментарий