Подробнее о Celery

Пока проверял как Celery работает с MySQL в качестве брокера, материала набежало на большую статью. Сергей Лебедев, спасибо за этот замечательный вопрос!

Проблемная область

В каждом более-менее крупном веб-проекте появляются задачи, которые не укладываются в короткий цикл запроса-ответа HTTP. Отправка уведомлений по почте - сервер может не отвечать 20 секунд, зачем же заставлять клиента ждать, можно отдать ему страничку "Письмо будет отправлено" и завершить отправку почту в фоновом (не-webserver) процессе. Отправка уведомлений поисковику и отправка pingbacks при создании новой статьи в блоге - в этих случаях тоже не обязательно заставлять пользователя ждать, когда запросы будут обработаны (может пройти тоже 10-20 секунд). Эти задачи можно выполнять в отдельных тредах, но нет гарантий, что они будут выполнены (например, веб-сервер может быть перестартован, когда задача будет выполнена наполовину, а после старта никто про задачу и не вспомнит).

Одним из первых и самых простых решений этой проблемы были ghetto queues - создавалась табличка в базе данных, туда складывались задания, а отдельная группа приложений последовательно их выполняла. Такой подход реализован в библиотеке ghettoq. Основной недостаток - трудность (а порой и невозможность) оперативной обработки задач, потому что получение задач из базы производится периодически (а если опрашивать базу данных слишком часто, то создается напрасная нагрузка на сервер даже в случае, когда задач в очереди нет).

С появлением open-source messaging systems (RabbitMQ, ActiveMQ, Stomp, ...) стало возможным исправить этот недостаток. В этих системах регистрируются очереди и их обработчики. При появлении новых сообщений в очереди, обработчики сразу же их получают. У этих систем стали появляться общие стандарты, самым распространенным из которых стал AMQP, они стали активно использоваться в веб-проектах.

Проект Celery позволяет описывать задачи на Python и запускать их на нескольких серверах, распределяя их с помощью брокера, в качестве которого может выступать RabbitMQ (рекомендованный вариант), Stomp, Redis и даже большинство СУБД (а точнее те, с которыми умеет работать Django ORM). Если задача возвращает какой-то полезный результат, то он может быть сохранен в СУБД, Redis или снова помещен в AMQP. Celery может тесно интегрировать с django-проектами, что не удивительно, поскольку он стартовал как django-приложение. Также Celery можно использовать и из любого другого python-фреймворка, если использование Django ORM в глубинах Celery не оскорбляет ваши религиозные убеждения.

Установка Celery

Устанавливаем через pip (кто еще не знает, что это такое, смотрите Pip, virtualenv и virtualenvwrapper):

$ pip install celery

Для программ celeryd, celeryinit нужно сделать символические ссылки, либо добавить путь к ним в PATH. Ниже - команды создания символических ссылок для Mac OS X + MacPorts:

$ sudo ln -s /opt/local/Library/Frameworks/Python.framework/Versions/2.6/bin/celeryd /usr/bin/celeryd
$ sudo ln -s /opt/local/Library/Frameworks/Python.framework/Versions/2.6/bin/celeryinit /usr/bin/celeryinit

Настройка брокера

Устанавливаем и настраиваем RabbitMQ. Привожу инструкции для Mac OS X + MacPorts:

$ sudo port install rabbitmq-server

Если у вас имя хоста задается DHCP-сервером (например: 124-23-11-02.starnet.ru), то нужно задать постоянное имя хоста:

$ sudo scutil --set HostName myhost.local

А затем отдактировать /etc/hosts:

127.0.0.1       localhost myhost myhost.local

Запускаем RabbitMQ:

$ sudo rabbitmq-server

Создаем пользователя и виртуальный хост RabbitMQ специально для Celery:

$ rabbitmqctl add_user celery celery
$ rabbitmqctl add_vhost celery_test
$ rabbitmqctl set_permissions -p celery_test celery "" ".*" ".*"

Создание и выполнение задач

Создаем директорию с тестовым проектом:

$ mkdir celery_test
$ cd celery_test
$ touch __init__.py

Создаем файл tasks.py с описанием задач:

from celery.decorators import task

@task
def square_sum(n):
    return sum(i*i for i in xrange(0, n+1))

Создаем файл celeryconfig.py и записываем настройки Celery:

BROKER_HOST = "localhost"
BROKER_PORT = 5672
BROKER_USER = "celery"
BROKER_PASSWORD = "celery"
BROKER_VHOST = "celery_test"
CELERY_BACKEND = "amqp"
CELERY_IMPORTS = ("tasks", )

Запускаем обработчик задач из текущей директории:

$ PYTHONPATH="." celeryd --loglevel=INFO

В соседней консоли заходим в эту директорию и запускаем python shell (рекомендую IPython):

>>> from tasks import square_sum
>>> result = square_sum.delay(100)
>>> print result.result

Celery, Carrot, MySQL

Если RabbitMQ поставить ну уж никак невозможно (например, не хватает памяти для Erlang-а, или места на диске, или прав), но есть старая добрая MySQL, то мы можем использовать проект Carrot от Ask Solem (автора Celery). Carrot - это AMQP Messaging Framework for Python, т.е. с его помощью мы сможем создать AMQP-обертку вокруг базы MySQL.

$ pip install -U ghettoq
$ mysql -u root
  mysql> create database celery_test character set = utf8;
  mysql> create user 'celery'@'localhost' identified by 'celery';
  mysql> grant all privileges on celery_test.* to 'celery'@'localhost';

Редактируем celeryconfig.py:

CARROT_BACKEND = "ghettoq.taproot.Database"
CELERY_IMPORTS = ("tasks", )

INSTALLED_APPS = ("ghettoq", )

DATABASE_ENGINE = 'mysql'
DATABASE_NAME = 'celery_test' 
DATABASE_HOST = ''
DATABASE_PORT = ''
DATABASE_USER = 'celery'
DATABASE_PASSWORD = 'celery'

Из текущей директории вызываем celeryinit (аналог manage.py syncdb из django):

$ PYTHONPATH="." celeryinit

Все готово, можно запускать рабочий процесс:

$ PYTHONPATH="." celeryd --loglevel=INFO

Отправляем задачи на выполнение так же, как и в примере с RabbitMQ. Я заметил, что задачи отрабатывают очень быстро - похоже используется какой-то способ получения уведомления о новой записи в таблице MySQL.

Бонус - задачи, выполняемые по расписанию

Кроме того, есть приятный бонус - можно создавать задачи, запускающиеся периодически (например, каждые полчаса). Эта функциональность не заменяет cron, но в одном из проектов мне как раз требовалось каждые 15 минут аггрегировать данные, а лезть ради этого в cron совсем не хотелось.

Подробнее смотрите в документации: Periodic Tasks, Ensuring a task is only executed one at a time и комплексный пример Tutorial: Creating a click counter using carrot and celery.

Заключение

Если хорошо знаешь экосистему своего любимого языка программирования, то полурабочих велосипедов становится меньше, а код становится проще и понятнее другим разработчикам (недавно я просматривал код django-проекта, который программист писал год, он не использовал ни одного стороннего приложения, всё написал сам; так делать не стоит). Другие важные строительные кирпичики python-проектов: Fabric, Pip, virtualenv, virtualevnwrapper.

© Блог Романа Ворушина