长时间运行的带有队列的REST API

时间:2015-10-08 07:49:42

标签: rest asynchronous message-queue long-running-processes

我们正在实施一个REST API,它将启动多个长时间运行的后端任务。我一直在阅读RESTful Web Services Cookbook,建议使用指向正在处理的任务的Content-Location标头返回HTTP 202 / Accepted。 (例如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 个答案:

答案 0 :(得分:49)

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

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

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