从芹菜任务接收事件

时间:2015-09-15 10:38:27

标签: django celery

我有一个长期运行的芹菜任务,它迭代一系列项目并执行一些操作。

任务应以某种方式报告当前处理的项目,以便最终用户了解任务的进度。

目前我的django应用程序和芹菜一起放在一台服务器上,所以我可以使用Django的模型来报告状态,但我计划添加更多远离Django的工作人员,所以他们无法到达DB。

现在我看到的解决方案很少:

  • 使用某些存储手动存储中间结果,例如redis或mongodb,然后通过网络提供。这让我有点担心,因为如果我将使用redis然后我应该保持同步Django端的代码读取状态和Celery任务写入状态,所以他们使用相同的密钥。
  • 使用REST调用从celery向Django报告状态。与PUT http://django.com/api/task/123/items_processed
  • 一样
  • 也许使用Celery事件系统并创建像Item processed这样的事件,django更新计数器
  • 创建一个单独的工作程序,该服务器在具有django的服务器上运行,该服务器包含的任务只增加items proceeded个计数,因此当任务完成时,它会发出increase_messages_proceeded_count.delay(task_id)

我提到的那些解决方案或隐藏的问题是什么?

5 个答案:

答案 0 :(得分:7)

实现目标可能有很多种方法,但我会这样做。

在长期运行的芹菜任务中,使用django's caching framework设置进​​度:

from django.core.cache import cache

@app.task()
def long_running_task(self, *args, **kwargs):
    key = "my_task: %s" % self.result.id
    ...
    # do whatever you need to do and set the progress
    # using cache:
    cache.set(key, progress, timeout="whatever works for you")
    ...

然后,您所要做的就是使用该密钥生成重复的AJAX GET请求,并从缓存中检索进度。这些方面的东西:

 def task_progress_view(request, *args, **kwargs):
     key = request.GET.get('task_key')
     progress = cache.get(key)
     return HttpResponse(content=json.dumps({'progress': progress}),
                         content_type="application/json; charset=utf-8")

这里有一个警告,如果您将服务器作为多个进程运行,请确保使用memcached之类的东西,因为django的本机缓存在进程之间会不一致。此外,我可能不会使用芹菜的task_id作为关键,但它足以用于演示目的。

答案 1 :(得分:2)

最简单的:

您的任务和django app已共享访问一个或两个数据存储 - 代理和结果后端(如果您使用的是与代理不同的数据存储)

您可以简单地将一些数据放入这些数据存储中的一个或另一个中,以指示任务当前正在处理哪个项目。

e.g。如果使用redis只需要一个关键的“任务 - 当前正在处理”并存储与正在处理的项目当前相关的数据。

答案 2 :(得分:1)

您可以使用类似Swampdragon的内容从Celery实例中联系到用户(您必须能够从客户端访问它,注意不要与CORS发生冲突)。它可以锁在柜台上,而不是模型本身。

答案 3 :(得分:1)

查看ArgumentNullException - Celery分布式任务队列的实时监控和Web管理员:

你需要它进行演示,对吧? flower适用于websockets。

例如 - 实时接收任务完成事件(取自官方文档):

Flower

您可能需要处理任务(' ws:// localhost:5555 / api / tasks /')。

我希望这会有所帮助。

答案 4 :(得分:0)

lehins' solution 看起来不错,如果您不介意您的客户反复轮询您的后端。这可能没问题,但随着客户数量的增加,它会变得昂贵。

Artur Barseghyan's solution 适合如果你只需要 Celery 内部机制生成的任务生命周期事件。

或者,您可以使用 Django Channels 和 WebSockets 实时向客户端推送更新。设置非常简单。

  1. channels 添加到您的 INSTALLED_APPS 并设置通道层。例如,使用 Redis 后端:
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("redis", 6379)]
        }
    }
}
  1. 创建一个事件使用者。这将从 Channels 接收事件并通过 Websockets 将它们推送到客户端。例如:
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebSocketConsumer


class TaskConsumer(WebsocketConsumer):
    def connect(self):
        self.task_id = self.scope['url_route']['kwargs']['task_id'] # your task's identifier
        async_to_sync(self.channel_layer.group_add)(f"tasks-{self.task_id}", self.channel_name)
        self.accept()

    def disconnect(self, code):
        async_to_sync(self.channel_layer.group_discard)(f"tasks-{self.task_id}", self.channel_name)

    def item_processed(self, event):
        item = event['item']
        self.send(text_data=json.dumps(item))
  1. 从 Celery 任务中推送事件,如下所示:
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer

...
async_to_sync(get_channel_layer.group_send)(f"tasks-{task.task_id}", {
    'type': 'item_processed',
    'item': item,
})

您还可以编写异步使用者和/或异步调用 group_send。无论哪种情况,您都不再需要 async_to_sync 包装器。

  1. websocket_urlpatterns 添加到您的 urls.py
websocket_urlpatterns = [
    path(r'ws/tasks/<task_id>/', TaskConsumer.as_asgi()),
]
  1. 最后,要在客户端中使用来自 JavaScript 的事件,您可以执行以下操作:
let task_id = 123;
let protocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
let socket = new WebSocket(`${protocol}${window.location.host}/ws/tasks/${task_id}/`);

socket.onmessage = function(event) {
    let data = JSON.parse(event.data);
    let item = data.item;
    // do something with the item (e.g., push it into your state container)
}