[ celery-django基本配置 ]


Celery官方文档

@app.task()

 通过使用app.task()装饰器,您可以轻松地从任何可调用的对象创建任务: 
from .models import User

@app.task
def create_user(username, password):
    User.objects.create(username=username, password=password)

 还可以为任务设置许多选项,这些选项可以指定为装饰器的参数:
 @app.task(serializer='json')
def create_user(username, password):
    User.objects.create(username=username, password=password)

绑定的任务
任务被绑定意味着任务的第一个参数将始终是任务实例(self),就像Python绑定方法一样:


多个装饰器
当将多个装饰器与任务装饰器结合使用时,必须确保任务装饰器是最后应用的(奇怪的是,在Python中,这意味着它必须在列表中的第一个): 
@app.task
@decorator2
@decorator1
def add(x, y):
    return x + y

任务继承
任务装饰器的基参数指定任务的基类:
import celery

class MyTask(celery.Task):

    def on_failure(self, exc, task_id, args, kwargs, einfo):
        print('{0!r} failed: {1!r}'.format(task_id, exc))

@app.task(base=MyTask)
def add(x, y):
    raise KeyError()

姓名
每个任务都必须有一个唯一的名称。
如果没有提供明确的名称,任务装饰器将为您生成一个,并且这个名称将基于1)任务在其中定义的模块,以及2)任务函数的名称。
设置显式名称的示例:
>>> @app.task(name='sum-of-two-numbers')
>>> def add(x, y):
...     return x + y
>>> add.name
'sum-of-two-numbers'

最佳实践是使用模块名称作为名称空间,这样,如果在另一个模块中已经定义了具有该名称的任务,则名称不会发生冲突。
>>> @app.task(name='tasks.add')
>>> def add(x, y):
...     return x + y


目录展示
project/
       /__init__.py
       /celery.py
       /moduleA/
               /__init__.py
               /tasks.py
       /moduleB/
               /__init__.py
               /tasks.py

日志记录
工作人员将自动为您设置日志记录,或者您可以手动配置日志记录。
有一个名为“celener.task”的特殊记录器,您可以从该记录器继承,以自动获取任务名称和唯一id作为日志的一部分。
最佳实践是在模块顶部为所有任务创建一个通用记录器:
from celery.utils.log import get_task_logger

logger = get_task_logger(__name__)
@app.task
def add(x, y):
    logger.info('Adding {0} + {1}'.format(x, y))
    return x + y

重试延迟
@app.task(bind=True, default_retry_delay=30 * 60)  # retry in 30 minutes.
def add(self, x, y):
    try:
        something_raising()
    except Exception as exc:
        # overrides the default delay to retry after 1 minute
        raise self.retry(exc=exc, countdown=60)

事务

from django.db import transaction
from django.http import HttpResponseRedirect

@transaction.atomic
def create_article(request):
    article = Article.objects.create()
    expand_abbreviations.delay(article.pk)
    return HttpResponseRedirect('/articles/')

这是一个Django视图,在数据库中创建一个文章对象,然后将主键传递给任务。它使用transaction.atomic装饰器,该装饰器将在视图返回时提交事务,或者在视图引发异常时回滚。
如果任务在事务提交之前就开始执行,则存在竞争条件;数据库对象还不存在!
解决方案是在成功提交所有事务后,使用on_commit回调来启动Celery任务。
from django.db.transaction import on_commit

def create_article(request):
    article = Article.objects.create()
    on_commit(lambda: expand_abbreviations.delay(article.pk))

在Django中基本使用官方文档

T.delay(arg, kwarg=value)
.apply_async. (.delay(*args, **kwargs) calls .apply_async(args, kwargs)). 的快捷方式

T.apply_async(countdown=10)
 从现在起10秒内执行。 

T.apply_async(eta=now + timedelta(seconds=10))
 从现在起10秒内执行,使用eta指定 

T.apply_async(countdown=60, expires=120)
 从现在起一分钟内执行,但在2分钟后过期。 

T.apply_async(expires=now + timedelta(days=2))
2天后过期,使用日期时间设置。 

task.delay(arg1, arg2, kwarg1='x', kwarg2='y')

task.apply_async(args=[arg1, arg2], kwargs={'kwarg1': 'x', 'kwarg2': 'y'})


# 链式调用执行完异步后执行自己的函数
add.apply_async((2, 2), link=add.s(16))
这里,第一个任务(4)的结果将被发送到一个新任务,该新任务将前一个结果加16,形成表达式(2+2)+16=20
如果任务引发异常(errback),您也可以导致应用回调。worker实际上不会将errback作为任务调用,而是直接调用errback函数,以便将原始请求、异常和回溯对象传递给它。
这是一个错误回调示例:
@app.task
def error_handler(request, exc, traceback):
    print('Task {0} raised exception: {1!r}\n{2!r}'.format(
          request.id, exc, traceback))

 可以使用link_error执行选项将其添加到任务中: 
add.apply_async((2, 2), link_error=error_handler.s())

[签名官方文档](https://docs.celeryq.dev/en/stable/userguide/canvas.html#guide-canvas "签名官方文档")
您可以使用 添加任务的名称为其创建签名,如下所示:
>>> from celery import signature
>>> signature('tasks.add', args=(2, 2), countdown=10)
tasks.add(2, 2)

也可以使用任务的 签名 方法:
>>> add.signature((2, 2), countdown=10)
tasks.add(2, 2)

还有一个使用星星参数的快捷方式:
>>> add.s(2, 2)
tasks.add(2, 2)



此外, link 和 link_error 选项都可以表示为 作为列表:
add.apply_async((2, 2), link=[add.s(16), other_task.s()])

ETA(预计到达时间)允许您设置特定的日期和时间, 是执行任务的最早时间。倒计时为 一种快捷方式,可将ETA设置为未来的秒数。
>>> result = add.apply_async((2, 2), countdown=3)
>>> result.get()    # this takes at least 3 seconds to return
20

当 倒计时 为整数时, eta 必须为日期时间 对象,指定精确的日期和时间(包括毫秒精度, 和时区信息):
>>> from datetime import datetime, timedelta

>>> tomorrow = datetime.utcnow() + timedelta(days=1)
>>> add.apply_async((2, 2), eta=tomorrow)

过期
>>> # Task expires after one minute from now.
>>> add.apply_async((10, 10), expires=60)

>>> # Also supports datetime
>>> from datetime import datetime, timedelta
>>> add.apply_async((10, 10), kwargs,
...                 expires=datetime.now() + timedelta(days=1)

使用组

指定调用任务时使用的压缩的示例:
results = []
with add.app.pool.acquire(block=True) as connection:
    with add.get_publisher(connection) as publisher:
        try:
            for args in numbers:
                res = add.apply_async((2, 2), publisher=publisher)
                results.append(res)
print([res.get() for res in results])

尽管这个特殊的例子更适合用一个群体来表达:
>>> from celery import group

>>> numbers = [(2, 2), (4, 4), (8, 8), (16, 16)]
>>> res = group(add.s(i, j) for i, j in numbers).apply_async()

>>> res.get()
[4, 8, 16, 32]

使用路由

task_routes = {
    'celery.ping': 'default',
    'mytasks.add': 'cpu-bound',
    'feed.tasks.*': 'feeds',                           # <-- glob pattern
    re.compile(r'(image|video)\.tasks\..*'): 'media',  # <-- regex
    'video.encode': {
        'queue': 'video',
        'exchange': 'media',
        'routing_key': 'media.video.encode',
    },
}

task_routes = ('myapp.tasks.route_task', {'celery.ping': 'default'})
---------------------------------
def route_task(self, name, args, kwargs, options, task=None, **kw):
        if task == 'celery.ping':
            return {'queue': 'default'}
---------------------------------
Task.apply_async(immediate=False, exchange='video',
                 routing_key='video.compress')
-------------------------------
and a router returns:路由器返回:
{'immediate': True, 'exchange': 'urgent'}
------------------------------
the final message options will be:最终消息选项将是:
immediate=False, exchange='video', routing_key='video.compress'
--------------------------------
定义值 任务路由 优先于中定义的值 任务队列 当合并两者时。
With the follow settings:具有以下设置:
task_queues = {
    'cpubound': {
        'exchange': 'cpubound',
        'routing_key': 'cpubound',
    },
}

task_routes = {
    'tasks.add': {
        'queue': 'cpubound',
        'routing_key': 'tasks.add',
        'serializer': 'json',
    },
}
The final routing options for tasks.add will become:
的最终路由选项 tasks.add 将变为:
{'exchange': 'cpubound',
 'routing_key': 'tasks.add',
 'serializer': 'json'}