Асинхронное распределенное выполнение задач. stdlib, celery,...
DESCRIPTION
Автор: Роман Иманкулов (doist.io) Когда необходимо заставить веб-приложение выполнить тяжелую работу без ухудшения user experience, или нужно по-быстрому собрать кластер на коленке, возникает потребность в распределенном выполнении очередей команд. В докладе будут рассмотрены варианты организации таких очередей подручными средствами, будет выяснено, чем так хорош Celery, есть ли у него достойные альтернативы, и как написать рабочий менеджер очередей в тридцать строчек кода.TRANSCRIPT
КЛАСТЕР НА КОЛЕНКЕCELERY, RQ, STDLIB И СОБСТВЕННЫЕ ВЕЛОСИПЕДЫ
Minsk Python Meetup, ноябрь 2013
/ Роман Иманкулов @rdotpy
ПАТТЕРН QUEUE
ПАТТЕРН QUEUE
CELERY — ТЯЖЕЛОВЕС АСИНХРОННОСТИ
CELERY — ИСПОЛЬЗОВАНИЕ ТРИВИАЛЬНОСоздали задачу (myworker.py)
from celery import Celeryapp = Celery('tasks', broker='redis://', backend='redis://')@app.taskdef sum(a, b): return a + b
Запустили worker
$ celery worker --app=myworker
CELERY — ИСПОЛЬЗОВАНИЕ ТРИВИАЛЬНОЗапустили задачу и получили результат
>>> async_result = sum.delay(2, 2)>>> print async_result.get()4
CELERY — ОТЛОЖЕННОЕ ВЫПОЛНЕНИЕКОМАНД
Задача будет выполнена не раньше, чем через 3 секунды
>>> sum.apply_async((2, 2), countdown=3).get()
CELERY — ПОВТОРНОЕ ВЫПОЛНЕНИЕКОМАНД
В случае неудачи задача будет выполнена повторно
@app.task(bind=True, max_retries=3)def get_url(self, url): try: return urllib2.urlopen(url).read() except IOError as exc: # попробовать выполнить задачу через 30 сек. # всего не более 3 попыток raise self.retry(exc=exc, countdown=30)
CELERY — ЗАМЕНА CRON-АОпределяем задачи для запуска
app.conf.CELERYBEAT_SCHEDULE = { 'add-every-30-seconds': { 'task': 'tasks.add', 'schedule': datetime.timedelta(seconds=30), 'args': (16, 16) },}
Запускаем Celery beat
$ celery beat
CELERY — ПРОДВИНУТЫЙ РОУТИНГЗапускаем несколько worker-ов
$ celery worker -Q premium$ nice celery worker # очередь по умолчанию
Распределяем задания по очередям
add.apply_async((2, 2), queue='premium')
CELERY CANVAS — ПЛАН АСИНХРОННЫХОПЕРАЦИЙ
Цепочка команд: асинхронное последовательное выполнение
>>> chain = (add.s(2, 2) | add.s(4) | add.s(8))>>> chain().get()16
Группа команд: асинхронное параллельное выполнение
>>> from celery import group>>> res = group(add.s(i, i) for i in xrange(10))()>>> res.get(timeout=1)[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
... a ещё chords, maps, starmaps и много чего ещё, что вам,возможно, никогда не пригодится.
RQ. ЛЕГКОВЕСНАЯАЛЬТЕРНАТИВА
RQ. ЗАПУСКАЕТ ВСЁСтарт worker-а
$ rqworker
Создаем очередь и отправляем в нее задачу
>>> q = rq.Queue(connection=redis.Redis())>>> job = q.enqueue(os.path.join, 'foo', 'bar')>>> print job.result'foo/bar'
RQ. ЗАПУСКАЕТ ВСЁ1. Redis в качестве брокера и result storage2. Нулевая настройка3. Запуск любых импортируемых функций4. Поддержка множества очередей5. Простейшая система зависимостей задач
http://python-rq.org
ЧЕМ ЖЕ ОСОБЕННО ХОРОШ RQ?
ВОТ ЭТИМ ГРАФИКОМ!
СОБСТВЕННЫЕ МЕНЕДЖЕРЫ ОЧЕРЕДЕЙ
ЗАЧЕМ?Потому что простые решения сложных задач — это круче, чем
сложные — простых
THE BEST CODE IS NO CODE AT ALLJeff Atwood, Stackoverflow
MULTIPROCESSINGзапускает функции python в отдельных процессахAPI как у threadingимеет примитивы для обмена данными (queue, pipe)имеет примитивы для работы с shared-объектамиумеет работать по сети
MULTIPROCESSING
https://gist.github.com/imankulov/bbb49b57fc15ad171fa9
REDISLPUSH, BRPOP И ТРИДЦАТЬ СТРОК КОДА
https://gist.github.com/imankulov/702967fd559f718b4f1c
RQ ПРОТИВ НАШИХ РЕШЕНИЙ