Как реализовать autoretry для задач сельдерея
В сельдерей, вы можете retry
любая задача в случае исключения. Вы можете сделать это так:
@task(max_retries=5)
def div(a, b):
try:
return a / b
except ZeroDivisionError, exc:
raise div.retry(exc=exc)
в этом случае, если вы хотите разделить на ноль, задача будет удалена пять раз. Но вы должны проверить наличие ошибок в коде явно. Задача не будет удалена, если вы пропустите try-except
заблокировать.
Я хочу, чтобы мои функции, чтобы выглядеть как:
@celery.task(autoretry_on=ZeroDivisionError, max_retries=5)
def div(a, b):
return a / b
4 ответов
Я искал эту проблему некоторое время, но нашел только эта функция запрос.
Я решаю написать свой собственный декоратор для выполнения авто-повторов:
def task_autoretry(*args_task, **kwargs_task):
def real_decorator(func):
@task(*args_task, **kwargs_task)
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except kwargs_task.get('autoretry_on', Exception), exc:
wrapper.retry(exc=exc)
return wrapper
return real_decorator
С этим декоратором я могу переписать свою предыдущую задачу:
@task_autoretry(autoretry_on=ZeroDivisionError, max_retries=5)
def div(a, b):
return a / b
сельдерей (начиная с версии 4.0) имеет именно то, что вы искали:
@app.task(autoretry_for=(SomeException,))
def my_task():
...
см.: http://docs.celeryproject.org/en/latest/userguide/tasks.html#automatic-retry-for-known-exceptions
я модифицировал ваш ответ для работы с существующим API сельдерея (в настоящее время 3.1.17)
class MyCelery(Celery):
def task(self, *args_task, **opts_task):
def real_decorator(func):
sup = super(MyCelery, self).task
@sup(*args_task, **opts_task)
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except opts_task.get('autoretry_on', Exception) as exc:
logger.info('Yo! We did it!')
wrapper.retry(exc=exc, args=args, kwargs=kwargs)
return wrapper
return real_decorator
тогда, в ваши задачи
app = MyCelery()
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
@app.task(autoretry_on=Exception)
def mytask():
raise Exception('Retrying!')
Это позволяет добавить функциональность autoretry_on к вашим задачам без использования отдельного декоратора для определения задач.
вот улучшенная версия существующих ответов.
это полностью реализует поведение сельдерея 4.2 (as документы), но для сельдерей 3.1.25.
Он также не нарушает различные формы декоратора задач (с/без скобок) и возвращает/поднимает правильно.
import functools
import random
from celery.app.base import Celery as BaseCelery
def get_exponential_backoff_interval(factor, retries, maximum, full_jitter=False):
"""
Calculate the exponential backoff wait time.
(taken from Celery 4 `celery/utils/time.py`)
"""
# Will be zero if factor equals 0
countdown = factor * (2 ** retries)
# Full jitter according to
# https://www.awsarchitectureblog.com/2015/03/backoff.html
if full_jitter:
countdown = random.randrange(countdown + 1)
# Adjust according to maximum wait time and account for negative values.
return max(0, min(maximum, countdown))
class Celery(BaseCelery):
def task(self, *args, **opts):
"""
Overridden to add a back-port of Celery 4's `autoretry_for` task args.
"""
super_method = super(Celery, self).task
def inner_create_task_cls(*args_task, **opts_task):
# http://docs.celeryproject.org/en/latest/userguide/tasks.html#Task.autoretry_for
autoretry_for = tuple(opts_task.get('autoretry_for', ())) # Tuple[Type[Exception], ...]
retry_backoff = int(opts_task.get('retry_backoff', False)) # multiplier, default if True: 1
retry_backoff_max = int(opts_task.get('retry_backoff_max', 600)) # seconds
retry_jitter = opts_task.get('retry_jitter', True) # bool
retry_kwargs = opts_task.get('retry_kwargs', {})
def real_decorator(func):
@super_method(*args_task, **opts_task)
@functools.wraps(func)
def wrapper(*func_args, **func_kwargs):
try:
return func(*func_args, **func_kwargs)
except autoretry_for as exc:
if retry_backoff:
retry_kwargs['countdown'] = get_exponential_backoff_interval(
factor=retry_backoff,
retries=wrapper.request.retries,
maximum=retry_backoff_max,
full_jitter=retry_jitter,
)
raise wrapper.retry(exc=exc, **retry_kwargs)
return wrapper
return real_decorator
# handle both `@task` and `@task(...)` decorator forms
if len(args) == 1:
if callable(args[0]):
return inner_create_task_cls(**opts)(*args)
raise TypeError('argument 1 to @task() must be a callable')
if args:
raise TypeError(
'@task() takes exactly 1 argument ({0} given)'.format(
sum([len(args), len(opts)])))
return inner_create_task_cls(**opts)
Я также написал некоторые модульные тесты для этого, как я использую его в своем проекте.
их можно найти в в этом суть но обратите внимание, что они не легко запускать-рассматривайте больше как документацию о том, как работает вышеуказанная функция (и проверку того, что она работает правильно).