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