简体   繁体   English

使用主题交换运行多个 Celery 任务

[英]Run multiple Celery tasks using a topic exchange

I'm replacing some homegrown code with Celery, but having a hard time replicating the current behaviour.我正在用 Celery 替换一些本土代码,但很难复制当前的行为。 My desired behaviour is as follows:我想要的行为如下:

  • When creating a new user, a message should be published to the tasks exchange with the user.created routing key.创建新用户时,应使用user.created路由键将消息发布到tasks交换。
  • Two Celery tasks should be trigged by this message, namely send_user_activate_email and check_spam .此消息应触发两个 Celery 任务,即send_user_activate_emailcheck_spam

I tried implementing this by defining a user_created task with a ignore_result=True argument, plus a task for send_user_activate_email and check_spam .我尝试通过使用ignore_result=True参数定义user_created任务以及send_user_activate_emailcheck_spam的任务来send_user_activate_email check_spam

In my configuration, I added the following routes and queues definitions.在我的配置中,我添加了以下路由和队列定义。 While the message is delivered to the user_created queue, it is not delivered to the other two queues.当消息传递到user_created队列时,它不会传递到其他两个队列。

Ideally, the message is only delivery to the send_user_activate_email and check_spam queues.理想情况下,消息仅传送到send_user_activate_emailcheck_spam队列。 When using vanilla RabbitMQ, messages are published to an exchange, to which queues can bind, but Celery seems to deliver a message to a queue directly.使用 vanilla RabbitMQ 时,消息被发布到一个交换器,队列可以绑定到该交换器,但 Celery 似乎直接将消息传递到队列。

How would I implement the behaviour outlined above in Celery?我将如何在 Celery 中实现上述行为?

CELERY_QUEUES = {
    'user_created': {'binding_key':'user.created', 'exchange': 'tasks', 'exchange_type': 'topic'},
    'send_user_activate_email': {'binding_key':'user.created', 'exchange': 'tasks', 'exchange_type': 'topic'},
    'check_spam': {'binding_key':'user.created', 'exchange': 'tasks', 'exchange_type': 'topic'},
}

CELERY_ROUTES = {
    'user_created': {
        'queue': 'user_created',
        'routing_key': 'user.created',
        'exchange': 'tasks',
        'exchange_type': 'topic',
    },
    'send_user_activate_email': {
        'queue': 'user_created',
        'routing_key': 'user.created',
        'exchange': 'tasks',
        'exchange_type': 'topic',
    },
    'check_spam': {
        'queue': 'user_created',
        'routing_key': 'user.created',
        'exchange': 'tasks',
        'exchange_type': 'topic',
    },
}

It sounds like you are expecting a single message to trigger/be consumed by two queues but this is not how Celery works.听起来您希望单个消息触发/被两个队列使用,但这不是 Celery 的工作方式。 An Exchange will post a task to eligible queues, but once it is consumed, the other Queues ignore the message. Exchange 会将任务发布到符合条件的队列,但是一旦它被消耗,其他队列就会忽略该消息。 You need a message per Task you want to trigger.每个要触发的任务都需要一条消息。

There is often confusion with new Celery users because there are two uses of "Queue" in this system; Celery 新用户经常会混淆,因为在这个系统中“队列”有两种用途; Kombu Queues which the Queue() and documentation refer to, and the AMQP Queues, which hold messages directly and are consumed by workers. Queue() 和文档所引用的 Kombu 队列,以及直接保存消息并由工作人员使用的 AMQP 队列。 When we publish to queues, we think of the AMQP ones, which is incorrect.当我们发布到队列时,我们想到的是 AMQP 的,这是不正确的。 (thanks to answer linked below). (感谢回答链接如下)。

Back to your issue, if I am understanding correctly, when user_created is consumed, you want it to spawn two more tasks;回到你的问题,如果我理解正确,当 user_created 被消耗时,你希望它产生两个以上的任务; send_user_activate_email and check_spam. send_user_activate_email 和 check_spam。 Furthermore, these should not be dependent on each other;此外,这些不应相互依赖; they can run in parallel on separate machines and do not need to know the status of one another.它们可以在不同的机器上并行运行,不需要知道彼此的状态。

In this case, you want user_created to "apply_async" these two new Tasks and return.在这种情况下,您希望 user_created “apply_async”这两个新任务并返回。 This could be done directly, or you can use a Celery "Group" containing check_spam and send_user_activate_email to achieve this.这可以直接完成,或者您可以使用包含 check_spam 和 send_user_activate_email 的 Celery“组”来实现此目的。 The group gives some nice shorthand and lends some structure to your tasks, so personally I'd nudge you that direction.该小组提供了一些不错的速记,并为您的任务提供了一些结构,因此我个人会向您推荐这个方向。

#pseudocode
group(check_spam.s(... checkspam kwargs ...), send_user_activate_email.s(... active email kwargs ...)).apply_async()

This setup would create four messages;此设置将创建四个消息; one for each Task you want to execute plus one for the Group(), which itself will have a result.一个用于您要执行的每个任务,另外一个用于 Group(),这本身就会产生结果。

In your case, I am not sure the Exchange or ignore_result is necessary, but I'd need to see the Task code and understand the system more to make that judgement.在您的情况下,我不确定 Exchange 或 ignore_result 是否必要,但我需要查看任务代码并更多地了解系统才能做出判断。

http://docs.celeryproject.org/en/latest/userguide/canvas.html#groups http://celery.readthedocs.org/en/v2.2.6/userguide/routing.html#exchanges-queues-and-routing-keys Why do CELERY_ROUTES have both a "queue" and a "routing_key"? http://docs.celeryproject.org/en/latest/userguide/canvas.html#groups http://celery.readthedocs.org/en/v2.2.6/userguide/routing.html#exchanges-queues-and-routing -keys 为什么 CELERY_ROUTES 有“队列”和“路由密钥”?

(if I am way off I'll delete/remove the answer...) (如果我离开我会删除/删除答案......)

The easy way to dessign and resolve your problem is usign Celery workflows.设计和解决问题的简单方法是使用 Celery 工作流程。
But first of all I'd change your queue definition, setting a unique routing key per task and exchange_type with 'direct' value.但首先,我会更改您的队列定义,为每个任务设置唯一的路由键,并使用“直接”值设置 exchange_type。

According with celery documentation , Direct exchanges match by exact routing keys , so we set the same exchange to all custom tasks and consumer queues and we map routing_key (for tasks) and binding_key (for queues) like the next snippet:根据celery 文档直接交换通过精确的路由键匹配,因此我们为所有自定义任务和消费者队列设置相同的交换,并像下一个片段一样映射 routing_key(用于任务)和 binding_key(用于队列):

CELERY_QUEUES = {
    'user_created': {'binding_key':'user_created', 'exchange': 'tasks', 'exchange_type': 'direct'},
    'send_user_activate_email': {'binding_key':'send_user_activate_email', 'exchange': 'tasks', 'exchange_type': 'direct'},
    'check_spam': {'binding_key':'check_spam', 'exchange': 'tasks', 'exchange_type': 'direct'},
}

CELERY_ROUTES = {
    'user_created': {
        'queue': 'user_created',
        'routing_key': 'user_created',
        'exchange': 'tasks',
        'exchange_type': 'direct',
    },
    'send_user_activate_email': {
        'queue': 'send_user_activate_email',
        'routing_key': 'send_user_activate_email',
        'exchange': 'tasks',
        'exchange_type': 'direct',
    },
    'check_spam': {
        'queue': 'check_spam',
        'routing_key': 'check_spam',
        'exchange': 'tasks',
        'exchange_type': 'direct',
    },
}

Once this change is done, you need to use the proper workflow for the available list ( http://docs.celeryproject.org/en/latest/userguide/canvas.html#the-primitives ).完成此更改后,您需要对可用列表 ( http://docs.celeryproject.org/en/latest/userguide/canvas.html#the-primitives ) 使用正确的工作流程。 Reading your problem I think you need a chain, because order is needed to be preserved.阅读您的问题,我认为您需要一个链,因为需要保留顺序。

sequential_tasks = []
sequential_tasks.append(user_created.s(**user_created_kwargs))
sequential_tasks.append(send_user_activate_email.s(**send_user_activate_email_kwargs))
sequential_tasks.append(check_spam.s(**check_spam_kwargs))
#you can add more tasks to the chain
chain(*sequential_tasks)()

Celery will handle queue-related-work transparently. Celery 将透明地处理与队列相关的工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM