如何从运行尽可能快的 CherryPy BackgroundTask 返回数据

Posted

技术标签:

【中文标题】如何从运行尽可能快的 CherryPy BackgroundTask 返回数据【英文标题】:How to return data from a CherryPy BackgroundTask running as fast as possible 【发布时间】:2016-08-07 07:09:35 【问题描述】:

我正在构建一个使用 CherryPy 对数据进行迭代批处理的 Web 服务。理想的工作流程如下:

    用户 POST 数据到服务进行处理 当处理作业空闲时,它会收集排队的数据并开始另一个迭代 在处理作业时,用户正在向队列中发布更多数据以进行下一次迭代 当前迭代完成后,将返回结果,以便用户可以使用相同的 API 获取它们。 使用下一批排队数据重新开始作业。

这里的关键考虑因素是处理应该尽可能快地运行,每次迭代在前一个迭代完成后立即开始,而不管队列中的数据量如何。每次迭代需要多长时间没有上限,因此我无法为其运行创建固定时间表。

有几个使用 BackgroundTask (like this one) 的示例,但我还没有找到一个处理返回数据的示例,或者一个处理尽可能快地运行而不是按固定时间表运行的任务的示例.

我不喜欢BackgroundTask 解决方案,所以如果有人能提供替代方案,我会非常高兴。不过感觉框架内有一个解决方案。

【问题讨论】:

【参考方案1】:

不要使用BackgroundTask 解决方案运行后台任务,因为它将在线程中运行,并且由于GIL,cherrypy 将无法回答新请求。使用在不同进程中运行后台任务的队列解决方案,例如Celery 或RQ。

我将详细开发一个使用 RQ 的示例。 RQ 使用 Redis 作为消息代理,所以首先你需要安装并启动 Redis。

然后使用长时间运行的后台方法创建一个模块(在我的示例中为mytask):

import time
def long_running_task(value):
    time.sleep(15)
    return len(value)

启动一个(或多个,如果您想并行运行任务)RQ 工作程序,运行工作程序的 python 可以访问您的 mytask 模块很重要(如果您的工作程序在运行之前导出 PYTHONPATH模块它不在路径中):

# rq worker

上面有一个非常简单的cherrypy webapp,它展示了如何使用RQ队列:

import cherrypy
from redis import Redis
from rq import Queue    
from mytask import long_running_task


class BackgroundTasksWeb(object):

    def __init__(self):
        self.queue = Queue(connection=Redis())
        self.jobs = []

    @cherrypy.expose
    def index(self):
        html =  ['<html>', '<body>']
        html += ['<form action="job">', '<input name="q" type="text" />', '<input type="submit" />', "</form>"]
        html += ['<iframe  src="/results" />']
        html += ['</body>', '</html>']
        return '\n'.join(html)

    @cherrypy.expose
    def results(self):
        html = ['<html>', '<head>', '<meta http-equiv="refresh" content="2" >', '</head>', '<body>']
        html += ['<ul>']
        html += ['<li>job: status: result: input:</li>'.format(j.get_id(), j.get_status(), j.result, j.args[0]) for j in self.jobs]
        html += ['</ul>']
        html += ['</body>', '</html>']
        return '\n'.join(html)

    @cherrypy.expose
    def job(self, q):
        job = self.queue.enqueue(long_running_task, q)
        self.jobs.append(job)
        raise cherrypy.HTTPRedirect("/")


cherrypy.quickstart(BackgroundTasksWeb())

在生产 web 应用中,我会使用 jinja2 模板引擎生成 html,并且很可能使用 websockets 来更新 web 浏览器中的作业状态。

【讨论】:

很好的答案,谢谢!我最终将它与类似的架构一起破解,但不像你所做的那样整洁。我没有听说过 Celery 或 RQ,但我会研究它们以供将来使用。

以上是关于如何从运行尽可能快的 CherryPy BackgroundTask 返回数据的主要内容,如果未能解决你的问题,请参考以下文章

如何从一个简单的网络应用程序中注销。在 CherryPy,Python

Cherrypy 如何处理用户线程?

win32com.client.Dispatch + Cherrypy = CoInitialize 没有被调用

如何在 CherryPy 的 POST 请求中接收 JSON?

部署 CherryPy(守护进程)

XML 格式的 fileDataBodyPart 未通过 ApacheHttpClient 上传到 CherryPy