Ответ 1
Из официальной документации: Обеспечение выполнения задачи выполняется только по одному за раз.
Я использую сельдерей для обновления RSS-каналов на моем сайте агрегации новостей. Я использую один файл @task для каждого фида, и все выглядит хорошо.
Там подробно, что я не уверен, что справиться хорошо: все каналы обновляются каждую минуту с помощью @periodic_task, но что, если фид все еще обновляется из последней периодической задачи при запуске нового? (например, если канал действительно медленный или офлайн, а задача удерживается в цикле повтора)
В настоящее время я храню результаты задач и проверяю их статус следующим образом:
import socket
from datetime import timedelta
from celery.decorators import task, periodic_task
from aggregator.models import Feed
_results = {}
@periodic_task(run_every=timedelta(minutes=1))
def fetch_articles():
for feed in Feed.objects.all():
if feed.pk in _results:
if not _results[feed.pk].ready():
# The task is not finished yet
continue
_results[feed.pk] = update_feed.delay(feed)
@task()
def update_feed(feed):
try:
feed.fetch_articles()
except socket.error, exc:
update_feed.retry(args=[feed], exc=exc)
Возможно, существует более сложный/надежный способ достижения того же результата с использованием какого-то механизма сельдерея, который я пропустил?
Из официальной документации: Обеспечение выполнения задачи выполняется только по одному за раз.
Основываясь на ответе MattH, вы можете использовать декоратор следующим образом:
def single_instance_task(timeout):
def task_exc(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
lock_id = "celery-single-instance-" + func.__name__
acquire_lock = lambda: cache.add(lock_id, "true", timeout)
release_lock = lambda: cache.delete(lock_id)
if acquire_lock():
try:
func(*args, **kwargs)
finally:
release_lock()
return wrapper
return task_exc
тогда используйте его так...
@periodic_task(run_every=timedelta(minutes=1))
@single_instance_task(60*10)
def fetch_articles()
yada yada...
Если вы ищете пример, который не использует Django, тогда попробуйте этот пример (caveat: вместо этого использует Redis, который я уже использовал).
Код декоратора выглядит следующим образом (полный кредит автору статьи, прочитайте его)
import redis
REDIS_CLIENT = redis.Redis()
def only_one(function=None, key="", timeout=None):
"""Enforce only one celery task at a time."""
def _dec(run_func):
"""Decorator."""
def _caller(*args, **kwargs):
"""Caller."""
ret_value = None
have_lock = False
lock = REDIS_CLIENT.lock(key, timeout=timeout)
try:
have_lock = lock.acquire(blocking=False)
if have_lock:
ret_value = run_func(*args, **kwargs)
finally:
if have_lock:
lock.release()
return ret_value
return _caller
return _dec(function) if function is not None else _dec
Использование https://pypi.python.org/pypi/celery_once, похоже, делает работу действительно приятной, включая ошибки отчетности и тестирование против некоторых параметров для уникальности.
Вы можете делать такие вещи, как:
from celery_once import QueueOnce
from myapp.celery import app
from time import sleep
@app.task(base=QueueOnce, once=dict(keys=('customer_id',)))
def start_billing(customer_id, year, month):
sleep(30)
return "Done!"
который просто нуждается в следующих настройках в вашем проекте:
ONCE_REDIS_URL = 'redis://localhost:6379/0'
ONCE_DEFAULT_TIMEOUT = 60 * 60 # remove lock after 1 hour in case it was stale
Это решение для сельдерея, работающего на одном хосте с большей степенью уверенности 1. Другие типы (без зависимостей, например redis) от разломов разломов на основе файлов не работают с concurrency больше 1.
class Lock(object):
def __init__(self, filename):
self.f = open(filename, 'w')
def __enter__(self):
try:
flock(self.f.fileno(), LOCK_EX | LOCK_NB)
return True
except IOError:
pass
return False
def __exit__(self, *args):
self.f.close()
class SinglePeriodicTask(PeriodicTask):
abstract = True
run_every = timedelta(seconds=1)
def __call__(self, *args, **kwargs):
lock_filename = join('/tmp',
md5(self.name).hexdigest())
with Lock(lock_filename) as is_locked:
if is_locked:
super(SinglePeriodicTask, self).__call__(*args, **kwargs)
else:
print 'already working'
class SearchTask(SinglePeriodicTask):
restart_delay = timedelta(seconds=60)
def run(self, *args, **kwargs):
print self.name, 'start', datetime.now()
sleep(5)
print self.name, 'end', datetime.now()