长时间运行的REST API与队列

75
我们正在实现一个REST API,它将启动多个长时间运行的后端任务。我一直在阅读《RESTful Web Services Cookbook》,它的建议是返回HTTP 202 / Accepted,并使用Content-Location标头指向正在处理的任务(例如http://www.example.org/orders/tasks/1234),并让客户端轮询此URI以获取长时间运行任务的更新。
这样做的想法是立即将REST API发布到队列中,然后由后台工作角色从队列中接收消息并启动多个后端任务,也使用队列。我看到这种方法存在的问题是如何为任务分配唯一ID,并随后让客户端通过向Content-Location URI发出GET请求来请求任务的状态。
如果REST API立即发布到队列,则可以生成GUID并将其附加为添加到队列的消息的属性,但是获取请求状态变得麻烦。
另一个选择是让REST API立即向数据库添加条目(假设是订单,具有新的订单ID),具有初始状态,然后随后将消息放入队列以启动后台任务,然后随后更新该数据库记录。 API将在Content-Location标头的URI中返回此新订单ID,供客户端在检查任务状态时使用。
先添加数据库条目,然后再添加消息到队列似乎很奇怪,但仅将请求添加到队列会使跟踪进度变得困难。
推荐的方法是什么?
非常感谢您的见解。
1个回答

98

我假设您的系统如下所示。您拥有一个REST服务来接收客户端请求。它将请求转换为业务逻辑可理解的命令并将这些命令放入队列中。您有一个或多个工作进程可以处理和删除这些命令,并将结果发送到REST服务,以便响应客户端。

您的问题是长时间运行的任务导致客户端连接超时,因此无法发送响应。因此,您可以在将命令放入队列后发送202已接受状态,并添加轮询链接,以便客户端能够轮询更改。您的任务具有多个子任务,因此存在进度,而不仅仅是挂起和完成状态变化。

  1. 如果要坚持使用轮询,则应创建一个新的REST资源,其中包含长时间运行任务的实际状态和进度。这意味着您必须将此信息存储在数据库中,以便REST服务能够响应如GET /tasks/23461/status之类的请求。这意味着当工作进程完成子任务或整个任务时,其必须更新数据库。
  2. 如果您的REST服务正在作为守护程序运行,则可以通过进度通知它,因此将任务状态存储在数据库中将不是工作进程的责任。这种类型的REST服务也可以在内存中存储信息。
  3. 如果您决定使用Websockets通知客户端,则可以创建通知服务。通过REST,您必须响应一个任务ID。之后,在websocket连接上发送此任务ID,以便通知服务将知道哪个websocket连接订阅了某个任务的事件。之后您就不再需要REST服务,只要客户端没有关闭连接,就可以通过websocket连接发送进度。
您可以按照以下方法组合这些解决方案。您可以让REST服务创建任务资源,以便您可以使用轮询链接访问进度。之后,您通过WebSockets连接发送202标识符并将其发送回。因此,您可以使用通知服务向客户端发送通知。在进度条方面,您的工作程序将通知REST服务,该服务将创建类似于GET /tasks/23461/status的链接,并通过通知服务将该链接发送给客户端。之后,客户端可以使用该链接更新其状态。
如果您的REST服务作为守护程序运行,则我认为最后一种方法是最好的解决方案。这是因为您可以将通知责任移交给专用的通知服务,该服务可以使用WebSockets、轮询、SSE或任何您想要的方式。它可以崩溃而不会杀死REST服务,因此REST服务将保持稳定和快速。如果您还在202中发送了手动更新链接,则客户端(假设为人为控制的客户端)也可以执行手动更新,因此如果通知服务不可用,您将拥有类似优雅降级的功能。您不必维护通知服务,因为它不会知道任何有关任务的信息,它只会向客户端发送数据。您的工作程序也不必知道如何发送通知和创建超链接。维护客户端代码也将更容易,因为它几乎是纯REST客户端。唯一的额外功能将是订阅通知链接,这不会经常更改。

非常感谢您的见解。我非常支持务实的解决方案,因此单个数据库方法似乎就足够了。如果API支持聚合请求,任务模型也可以扩展以包括子任务。谢谢! - user2079172
谢谢您的更新和建议! :) 这个设置肯定是可行的解决方案。我们的集成商可能无法保持连接开放(传统系统),但很可能能够进行轮询。对于我来说,将REST API和202/Accepted方法与服务总线/队列桥接是有问题的。理想情况下,服务总线/队列应该是其他系统的集成点,在这种情况下,API只是另一种用于将另一个系统与服务总线(通道适配器)集成的方式。为了使您建议的设置起作用,我们需要另一个抽象层来处理与服务总线的集成。 - user2079172
我的理解是更新事件的更新方式。因此,API应该:1.在update_event表中插入新行并返回新id。2:立即在服务总线队列中插入新请求。现在客户端可以轮询等等。工作角色1:拾取队列消息并分派后端工作流程。2:完成后,它必须使用新的资源URI等更新update_event表。问题在于,工作角色将意识到具有特殊要求的客户端,或者?这意味着,它不再适用于通用的消息系统? - user2079172
1
@user2079172,您能详细说明一下“工作角色将意识到具有特殊要求的客户端”这部分吗? - inf3rno
2
感谢您抽出时间来帮忙。我找到了一篇有趣的文章,其中提供了类似的解决方案。我认为这篇文章最大的启示是将“任务”本身视为资源,而不是进入队列的请求,从而在REST API的上下文中将“任务”提升为一个完整的资源。使用“任务”资源的表意味着更多的工作要让工作角色从表中获取任务,并支持“最多一次”的逻辑,以便在扩展工作程序时使用。提到的文章:http://billhiggins.us/blog/2011/04/27/resty-long-ops/ - user2079172
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接