【问题标题】:Job processing via web application: real-time status updates and backend messaging通过 Web 应用程序处理作业:实时状态更新和后端消息传递
【发布时间】:2012-10-04 18:57:51
【问题描述】:

我想实现一个(开源)网络应用程序,用户通过浏览器向 Python 网络应用程序发送某种请求。请求数据用于定义和提交某种繁重的计算工作。计算工作外包给“工人后端”(也是 Python)。在作业处理期间,作业会随着时间的推移经历不同的阶段(理想情况下,从“提交”到中间状态到“完成”)。我想要完成的是实时向用户显示当前的工作状态。这意味着工作人员后端必须将作业状态传回 Web 应用程序。然后,Web 应用程序必须将信息推送到用户的浏览器。我为您带来了一张示意性描述基本思想的图片:

红色圆圈中的数字表示事件的时间顺序。 “web app”和“worker backend”仍有待设计。现在,如果您能帮助我做出一些技术决策,我将不胜感激。

我的问题,具体来说:

  1. 我应该在 Web 应用程序和工作人员后端之间应用哪种消息传递技术? 当工作人员后端发出关于某个工作的信号(某种消息)时,它必须触发 Web 应用程序中的某个事件。因此,我需要某种与最初请求提交作业的客户端相关联的回调。我想我需要一些发布/订阅机制,工作后端发布和网络应用订阅。当 Web 应用程序收到消息时,它会通过向客户端发送状态更新来对其做出反应。我希望工作人员后端具有可扩展性,并且与 Web 应用程序紧密分离。因此,我正在考虑使用 Redis 或 ZeroMQ 来完成这项任务。你怎么看?我的整个方法是不是有点太复杂了?

  2. 我应该使用哪种技术将信息推送到浏览器? 只是出于完美主义,我想要实时更新。我不想以高频率轮询。当工作人员后端发出消息时,我希望立即推送到客户端:-)。另外,我不需要最大的浏览器支持。这个项目首先对我来说或多或少是一个技术演示。我应该去 HTML5 服务器发送的事件/websockets 吗?或者你会推荐其他方式吗?

非常感谢您提前提出的建议。

【问题讨论】:

    标签: python web-applications websocket messaging zeromq


    【解决方案1】:

    一个选项是使用 WebSocket。如果你走这条路,你可能会查看Autobahn,它包括用于 Python (Twisted) 的客户端和服务器,以及基于 WebSocket 的 RPC+PubSub 协议(带有用于 Python、JavaScript 和 Android 的库)。使用 RPC+PubSub 订阅可以保护大量工作,并且可能满足您的需求(工作提交 => RPC,工作工作更新 => PubSub)。

    AutobahnPython 在 Twisted 上运行,它还可以充当 WSGI 容器,从而可以运行 Flask(或其他基于 WSGI 的 Web 框架)。您可以在 1 个端口/服务器上运行所有内容。后者在 GitHub Autobahn 存储库中有一个示例。

    免责声明:我是 Autobahn 和 WAMP 的原作者,为 Tavendo 工作。

    详情: 我假设你的工人做 CPU 密集型和/或阻塞的东西。

    首先,您的工作人员是纯 Python 还是外部程序?

    如果是后者,您可以使用 Twisted 进程协议实例,该实例通过 stdio 管道(以非阻塞方式)从主 Twisted 线程进行通信。如果是前者,可以使用 Twisted 后台线程池,使用 Twisted deferToThread(见:http://twistedmatrix.com/documents/current/core/howto/threading.html)。

    Autobahn 在 Twisted reactor 主线程上运行。如果您的工作人员也这样做(参见之前的 cmets),那么您可以直接调用 WebSocket/WAMP 工厂/协议实例上的方法。如果不是(worker 在后台线程上运行),您应该通过 callFromThread 调用这些方法。

    如果您使用 WAMP,主要是为每个工作人员获取 WampServerFactory 的引用。然后,worker 可以通过调用适当的工厂方法向所有订阅者发送 PubSub 事件。

    【讨论】:

    • 谢谢,oberstet。您能否详细说明如何实现工作人员后端 - Web 应用程序通信?
    【解决方案2】:

    为了有任何用途,您的 Web 应用程序将有一个数据库。我会在该数据库中创建一个专门用于这些作业的表。每个工作都有一个“状态”。

    这简化了您的系统,因为您只需发送开始工作的请求并将其交给后端工作人员(zmq 是这个 IMO 的一个很好的解决方案)。由于您使用 python 作为后端,因此让您的工作人员作业更新其在数据库中的当前工作作业或拥有另一个“更新程序”,其唯一工作是更新数据库中的字段(保持逻辑分开)是非常简单的将提供更好的解决方案,如果您进行大量更新,则允许您启动多个“更新程序”)

    那么对于您的前端,由于您不想轮询服务器,我会做一些'long poll' 的事情。您实际上所做的是轮询服务器,但服务器实际上永远不会“响应”,直到您感兴趣的数据发生变化。一旦发生变化,您就会响应请求。在前端,您的 JS 在收到最新更新后立即重新建立连接。只要您使用跨浏览器的 JS 框架(我建议使用 jQuery),此解决方案就可以跨浏览器兼容。


    要消除 Web 应用程序数据库轮询,请执行以下操作:

    将初始请求作为对 Web 应用程序的长轮询请求,Web 应用程序向您的后端发送一条 zmq 消息(可能需要使用 REQ/REP 套接字完成)并等待。它一直等到它从 zmq 后端收到状态更改的消息。当它获得状态更改时,它会以更改响应前端。此时,前端将发送一个新的长轮询请求(此作业的当前 id 可以是其身份),Web 应用程序将重新连接到后端并等待另一个状态更改。完成这项工作的诀窍是在最初创建套接字时(在第一个请求中)使用 ZMQ 的 ZMQ_IDENTITY 作为套接字。这将允许 Web 应用程序重新连接到相同的后端套接字并获得新的更新。当后端有新的更新要发送时,它会向 Web 应用程序发出信号,然后 Web 应用程序将响应长轮询请求及其状态更改。这样就没有polling,没有后端数据库,一切都是后端工作人员的事件驱动。

    我会设置某种看门狗,如果前端消失(切换页面或关闭浏览器),后端套接字将正确关闭。当它改变状态时,他们不需要坐在那里无限期地阻塞。

    【讨论】:

    • 感谢您的回答。对,对于这个问题,长轮询可能“足够”了。你能想出一个好方法来实现当 zmq 消息从 worker 后端到达 web 应用程序时触发向浏览器发送响应的“回调行为”吗?
    • 在我描述的解决方案中,后端和 Web 应用程序之间没有 zmq 消息。后端工作人员(他们自己或通过另一个数据库后端部分)直接使用其当前作业状态更新数据库。 Web 应用程序本质上是轮询数据库以查找前端发送其长轮询请求的作业中的状态更改。美妙之处在于服务器上几乎没有延迟(数据库通常托管在与 Web 服务器相关的本地主机上),并且对于前端来说基本上是实时的。
    • 因此您的计划包括从 Web 应用程序内频繁轮询数据库。几个月前,我已经使用 Redis DB 实现了一个原型,该数据库保存作业状态并经常轮询以识别作业状态更改。虽然这可行,但我认为这不是一个优雅的解决方案。我不需要用于其他目的的数据库。在我看来,一个优雅的解决方案是纯粹基于事件的,没有多余的轮询。
    • 检查答案,我添加了更多信息,这些信息将消除 Web 应用程序轮询并消除对数据库的需求。为每个作业使用唯一的 ID,并将其用于 ZMQ_IDENTITY。将其传递给浏览器进行第一次请求,前端将能够通过网络应用程序基于此“身份”一般地请求当前状态。
    • 所有答案都很棒,在系统设计过程中对我有帮助。你会得到绿色复选标记,指出在这种情况下,我只需要长轮询。
    【解决方案3】:

    既然你在谈论一个 python web 应用程序,我建议你看看:

    我应该在网络应用和工作人员后端之间应用哪种消息传递技术?

    Celery - 将您的工作分解为较小的任务,这些任务会返回需要显示给客户的结果

    我应该使用哪种技术将信息推送到浏览器?

    Socket IO NodeJS 类型的服务器端 JS 框架或 web socket library for you python web framework

    如果你不是太依赖python,看看Meteor

    基于this thread,其他实时更新从服务器到Web客户端进度的方法包括将进度状态写入redis数据库或使用Oribited/Morbid(均基于Twisted)使用STOMP protocol 基于来自 celery's subtasks 的异步结果

    【讨论】:

    • 感谢您的回答和推荐 Celery。但是,这可能是矫枉过正。我将不得不进一步研究它。你认为有一种方法可以使用 Celery 来传达中间工作状态吗?我不想在网络应用程序和工作后端之间有一个乒乓球,即我宁愿不把我的工作分成子工作。如果我选择 websockets,我想我会使用 gevent 和 gevent-websocket 或 ws4py。
    • 也许网络套接字和 gevent 是更好的方法。我刚刚添加了一些关于如何使用 celery 和基于 STOMP 的消息传递来实现相同目的或将作业状态写入数据库的更多信息,该数据库可以通过短轮询或其他方式更新给客户端。
    猜你喜欢
    • 2018-03-21
    • 1970-01-01
    • 2020-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多