【celery】User Guide[用户指南]--Tasks
Tasks
任务是Celery applications的构建块。
任务是可以通过任何调用创建的类。它执行双重角色,它定义了任务被调用时发生的情况(发送消息),以及当工作人员接收到该消息时会发生什么。
每个任务类都有一个惟一的名称,并且这个名称在消息中被引用,这样工作者就可以找到执行的正确函数。
任务消息不会从队列中删除,直到该消息被工作人员确认为止。一个工人可以提前预定很多信息,即使工人被断电或其他原因导致死亡,信息也会被重新传递给另一个工人。
理想情况下,任务函数应该是幂等的:也就是说,即使多次使用相同的参数,函数也不会产生无意的效果。由于工作人员无法检测到您的任务是否具有幂等性,默认的行为是在执行之前预先确认消息,这样就不会再次执行已启动的任务调用。
如果任务是幂等的,可以设置acks_late选项,让工作人员在任务返回后确认消息。请参见FAQ条目,我应该使用retry或acks_late吗?
注意,如果执行任务的子进程被终止(或者通过任务调用sys.exit(),或者通过signal),即使在启用acks_late时,worker也会确认消息。这种行为的目的是……:
- 我们不希望重新运行那些迫使内核发送SIGSEGV(分割错误)或类似的信号的任务。
- 我们假设系统管理员故意杀死任务,不希望它自动重启。
- 一个分配太多内存的任务有触发内核OOM杀手的危险,同样的事情可能会再次发生。
- 当重新交付时总是失败的任务可能会导致系统的高频消息循环。
如果您真的希望在这些场景中重新交付任务,您应该考虑启用task_reject_on_worker_lost设置。
在本章中,您将学习如何定义任务,这是内容表:
- Basics
- Names
- Task Request
- Logging
- Retrying
- List of Options
- States
- Semipredicates
- Custom task classes
- How it works
- Tips and Best Practices
- Performance and Strategies
- Example
Basics
通过利用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)
多个修饰符
在任务修饰符与多个修饰符组合使用时,必须确保任务修饰符是最后一个。
@app.task @decorator2 @decorator1 def add(x, y): return x + y
如何导入任务装饰器?“应用”是什么?
如果您正在使用Django(请参阅Django的第一步),或者您是一个库的作者,那么您可能想使用shared_task() 来装饰:
from celery import shared_task @shared_task def add(x, y): return x + y
Bound tasks(绑定的任务)
被绑定的任务意味着任务的第一个参数始终是任务实例(self),就像Python绑定方法一样:
logger = get_task_logger(__name__) @task(bind=True) def add(self, x, y): logger.info(self.request.id)
要重新尝试(使用app.Task.retry())、访问关于当前任务请求的信息,以及添加到自定义任务基类的任何附加功能,都需要绑定任务。
Task inheritance(任务继承)
任务修饰符的基本参数指定任务的基类:
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)) @task(base=MyTask) def add(x, y): raise KeyError()
Names
每个任务都必须有一个惟一的名称。
如果没有提供显式名称,任务decorator将为您生成一个名称,这个名称将基于任务定义的模块和任务函数的名称。
示例设置明确的名称:
>>> @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
您可以通过调查它的.name属性来告知任务的名称:
>>> add.name 'tasks.add'
我们在这里指定的名称(tasks.add)正是在名为tasks.py的模块中定义任务时自动生成的名称。
tasks.py:
@app.task def add(x, y): return x + y
>>> from tasks import add >>> add.name 'tasks.add'
自动命名和相关导入
相对导入和自动命名生成一起不太好,所以如果您使用相对导入,您应该显式地设置名称。
例如,如果客户端导入模块“myapp.tasks”为".tasks“,并且工作人员将模块导入为“myapp.tasks“,生成的名称将不匹配,并且工作人员将会提高一个未出现的错误。
这也是在使用Django时。在INSTALLED_APPS 加入project.myapp-style:
INSTALLED_APPS = ['project.myapp']
If you install the app under the name project.myapp then the tasks module will be imported as project.myapp.tasks, so you must make sure you always import the tasks using the same name:
>>> from project.myapp.tasks import mytask # << GOOD >>> from myapp.tasks import mytask # << BAD!!!
第二个示例将导致任务以不同的方式命名,因为工人和客户端以不同的名称导入模块:
>>> from project.myapp.tasks import mytask >>> mytask.name 'project.myapp.tasks.mytask' >>> from myapp.tasks import mytask >>> mytask.name 'myapp.tasks.mytask'
出于这个原因,您必须在导入模块的过程中保持一致,这也是Python的最佳实践。
类似地,您不应该使用旧式的相对导入:
from module import foo # BAD! from proj.module import foo # GOOD!
新型的相对导入很好,可以使用:
from .module import foo # GOOD!
如果您想要在一个已经广泛使用这些模式的项目中使用celery,并且您没有时间重构现有的代码,那么您可以考虑明确地指定名称,而不是依赖于自动命名:
@task(name='proj.tasks.add') def add(x, y): return x + y
改变自动命名行为(Changing the automatic naming behavior)
新的4.0版本中
有些情况下,默认的自动命名是不合适的。假设您在许多不同的模块中有许多任务:
project/
/__init__.py
/celery.py
/moduleA/
/__init__.py
/tasks.py
/moduleB/
/__init__.py
/tasks.py
使用默认的自动命名,每个任务都有一个生成的名称,比如modulea.tasks.taskA, moduleA.tasks.taskB, moduleB.tasks.test,等等;您可能想要删除所有任务名称中的任务。正如上面所指出的,您可以显式地为所有任务指定名称,或者您可以通过重写app.gen taskname()来更改自动命名行为。
from celery import Celery class MyCelery(Celery): def gen_task_name(self, name, module): if module.endswith('.tasks'): module = module[:-6] return super(MyCelery, self).gen_task_name(name, module) app = MyCelery('main')
每个任务都有一个名字,比如moduleA.taskA,moduleA.taskB,moduleB.test。
Task Request(任务请求)
app.Task.request包含与当前执行任务相关的信息和状态。
该请求定义了以下属性:
id:执行任务的唯一id。
group: The unique id of the task’s group, if this task is a member.(任务组的唯一id,如果该任务是成员)
chord: chord的唯一id 是 属于这个任务的(如果任务是标题的一部分)
correlation_id: Custom ID used for things like de-duplication(用于删除重复的自定义ID)。
args: 位置参数。
kwargs:关键字参数。
origin: 发送该任务的主机名。
retries:当前的任务已经被重试了多少次。一个从0开始的整数。
is_eager: 如果任务是在客户端本地执行的,而不是由工作人员执行的,则设置为True。
eta:The original ETA of the task (if any). This is in UTC time (depending on the enable_utc setting).
expires:The original expiry time of the task (if any). This is in UTC time (depending on the enable_utc setting).
hostname:执行任务的worker实例的节点名。
delivery_info:额外的消息传递信息。这是一个包含用于交付该任务的交换和路由键的映射。例如使用app.Task.retry()将任务重新发送到相同的目标队列这一段中键的可用性取决于所使用的message broker。
reply-to:用于发送回复的队列名称(与RPCresult后端一起使用)
called_directly:如果该任务不是由工人执行的,那么该标记将被设置为true
timelimit: A tuple of the current (soft, hard) time limits active for this task (if any).
callbacks:如果该任务成功返回,将调用一个签名列表
errback:如果该任务失败,将调用一个签名列表

浙公网安备 33010602011771号