Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/django/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Django 从芹菜任务接收事件_Django_Celery - Fatal编程技术网

Django 从芹菜任务接收事件

Django 从芹菜任务接收事件,django,celery,Django,Celery,我有一个长时间运行的芹菜任务,它迭代一系列项目并执行一些操作 任务应该以某种方式报告它当前正在处理的项目,以便最终用户知道任务的进度 目前,我的django应用程序和芹菜一起位于一台服务器上,因此我可以使用django的模型来报告状态,但我计划添加更多远离django的工作人员,因此他们无法访问DB 现在我看到的解决方案很少: 使用一些存储设备(如redis或mongodb)手动存储中间结果,然后通过网络提供。这让我有点担心,因为例如,如果我要使用redis,那么我应该保持Django端读取状

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

任务应该以某种方式报告它当前正在处理的项目,以便最终用户知道任务的进度

目前,我的django应用程序和芹菜一起位于一台服务器上,因此我可以使用django的模型来报告状态,但我计划添加更多远离django的工作人员,因此他们无法访问DB

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

  • 使用一些存储设备(如redis或mongodb)手动存储中间结果,然后通过网络提供。这让我有点担心,因为例如,如果我要使用redis,那么我应该保持Django端读取状态的代码与芹菜任务写入状态的代码同步,因此它们使用相同的键
  • 使用REST调用从芹菜向Django报告状态。像
    PUThttp://django.com/api/task/123/items_processed
  • 可能使用芹菜事件系统,创建类似于
    已处理项目
    的事件,django会在这些事件上更新计数器
  • 创建一个单独的worker,该worker在使用django的服务器上运行,django持有一个只增加
    已处理项目数的任务,因此当任务使用一个项目完成时,它会发出
    增加已处理消息数。延迟(任务id)
我提到的问题是否有解决方案或隐藏问题?

最简单的:

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

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


e、 g.如果使用redis只是有一个“当前正在处理的任务”键,并将与当前正在处理的项目相关的数据存储在其中。

您可以使用类似于从芹菜实例到达用户的东西(您必须能够从客户端到达它,小心不要与您发生冲突)。它可以锁在柜台上,而不是模型本身。

看一下
flower
-芹菜分布式任务队列的实时监视器和web管理员:

你需要它做演示,对吗<代码>花
与WebSocket一起使用

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

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


我希望这能有所帮助。

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

在长时间运行的芹菜任务中,使用以下方法设置进度:

然后,您所要做的就是使用该键发出一个重复的ajaxget请求,并从缓存中检索进度。大致如下:

 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的本机缓存在进程之间不一致。此外,我可能不会使用芹菜的
任务id
作为键,但它足以用于演示目的。

如果您不介意客户反复轮询您的后端,看起来不错。这可能没问题,但随着客户数量的增长,成本会越来越高

如果您只需要西芹内部机器生成的任务生命周期事件,则此选项非常适用

或者,您可以使用和WebSocket将更新实时推送到客户端。设置非常简单

  • 频道
    添加到您的
    已安装的应用程序
    并设置频道层。例如,使用Redis后端:
  • 创建事件使用者。这将从通道接收事件,并通过WebSocket将它们推送到客户端。例如:
  • 从芹菜任务中推送事件,如下所示:
  • 您还可以编写异步使用者和/或异步调用
    group\u send
    。在这两种情况下,您都不再需要
    async\u to\u sync
    包装器

  • websocket\u urlpatterns
    添加到您的
    url.py

  • 没错,我可以通过手动维护一些计数器来利用redis/mongo/mysql。我现在就是这么做的。我想的是如何利用MQ芹菜已经使用的资源。因此,每个任务都会产生一些事件,如
    item
    ,这些事件将在django所在的服务器上处理。因为当项目处理结束时,计数器不是我唯一需要做的事情。可能还有更多任务处理项不知道的东西需要处理。我想你说过数据库(mysql)对工作人员不可用,但现在是这样,但这正是我想要避免的。目前我需要一个简单的计数器,显示用户处理了多少项。但在近期功能中,我需要更多。芹菜已经使用了酒吧/酒吧redis,所以我认为有一种方法可以在芹菜中实现这一点。它有一个事件系统,但事件类型的数量非常有限,而且它们都与芹菜监控有关。否则我需要手动使用redis,这不是很好,因为它只是一个计数器-是的。但是,我需要创建一个发布/订阅频道,这样任何人都可以订阅“项目继续”活动,而不仅仅是一个只增加计数器的功能。因此,我需要创建一个类似于observer/observable的抽象,这样就可以订阅
    事件和其他事件来触发事件等等。所以我必须创建一个合适的基础设施,它仅限于芹菜提供的活动。对于任务开始/完成的事件,我不需要花。我需要自定义事件,比如任务完成的第X阶段。Flower甚至不记录自定义状态。e、 g“进度”状态更新未反映在Flower中。我想,询问者(和我)想要的是报告类似于
    
    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")
        ...
    
     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")
    
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": [("redis", 6379)]
            }
        }
    }
    
    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))
    
    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,
    })
    
    websocket_urlpatterns = [
        path(r'ws/tasks/<task_id>/', TaskConsumer.as_asgi()),
    ]
    
    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)
    }