如何为Celery任务实现自动重试

时间:2013-07-31 19:52:44

标签: python celery

在Celery中,如果发生异常,您可以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 个答案:

答案 0 :(得分:10)

我搜索了这个问题一段时间,但发现只有this feature request

我决定编写自己的装饰器进行自动重试:

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

答案 1 :(得分:7)

Celery(自4.0版开始)正是您所寻找的:

@app.task(autoretry_for=(SomeException,))
def my_task():
    ...

请参阅:http://docs.celeryproject.org/en/latest/userguide/tasks.html#automatic-retry-for-known-exceptions

答案 2 :(得分:2)

我已修改your answer以使用现有的Celery 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功能添加到任务中,而无需使用单独的装饰器来定义任务。

答案 3 :(得分:0)

这是现有答案的改进版本。

这完全实现了Celery 4.2行为(如documented here),但对于Celery 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)

我也为此编写了一些单元测试,就像在项目中使用它一样。

可以在this gist中找到它们,但请注意,它们不容易运行-可以将其更多地用作上述功能的工作方式文档(以及对其是否正常工作的验证)。