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

Posted

技术标签:

【中文标题】带有队列的长时间运行的 REST API【英文标题】:Long running REST API with queues 【发布时间】:2016-01-05 17:13:05 【问题描述】:

我们正在实现一个 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】:

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

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

    如果你想坚持轮询,你应该创建一个新的 REST 资源,其中包含实际状态和长时间运行任务的进度。这意味着您必须将此信息存储在数据库中,以便 REST 服务能够响应 GET /tasks/23461/status 之类的请求。这意味着您的工作人员必须在完成子任务或整个任务时更新数据库。 如果您的 REST 服务作为守护程序运行,那么您可以通过进度通知它,因此将任务状态存储在数据库中将不是工作人员的责任。这种 REST 服务也可以将信息存储在内存中。 如果您决定使用 websockets 通知客户端,那么您可以创建通知服务。通过 REST,您必须使用任务 ID 进行响应。之后,您在 websocket 连接上发回此任务 ID,因此通知服务将知道哪个 websocket 连接订阅了某个任务的事件。之后就不需要REST服务了,只要客户端不关闭连接就可以通过websocket连接发送进度。 您可以通过以下方式组合这些解决方案。您让 REST 服务创建任务资源,因此您将能够通过使用轮询链接访问进度。之后,您通过 websockets 连接发回带有 202 的标识符。因此,您可以使用通知服务来通知客户端。通过进度,您的工作人员将通知 REST 服务,该服务将创建一个类似 GET /tasks/23461/status 的链接,并通过通知服务将该链接发送给客户端。之后,客户端可以使用该链接更新其状态。

如果您的 REST 服务作为守护程序运行,我认为最后一个是最佳解决方案。这是因为您可以将通知职责转移到专用的通知服务上,该服务可以使用 websockets、轮询、SSE 等任何您想要的东西。它可以在不杀死 REST 服务的情况下崩溃,因此 REST 服务将保持稳定和快速。如果您也使用 202 发回手动更新链接,则客户端可以进行手动更新(假设是人工控制的客户端),因此如果通知服务不可用,您将遇到类似优雅降级的情况。您不必维护通知服务,因为它对任务一无所知,它只会向客户端发送数据。您的工作人员不必知道如何发送通知以及如何创建超链接。维护客户端代码也会更容易,因为它几乎是一个纯 REST 客户端。唯一的额外功能是订阅通知链接,它不会经常更改。

【讨论】:

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

以上是关于带有队列的长时间运行的 REST API的主要内容,如果未能解决你的问题,请参考以下文章

Spring REST - 经过较长的空闲时间后,第一次调用需要很长时间(5-10 秒)

如何在微服务中处理从 UI 到 API 的 REST 调用

Spring + Hibernate+ HikariCP:如何在进行长时间运行的 REST 调用时处理数据库连接?

在 Grails 应用程序中为长时间运行的作业构建队列的最佳方法是啥?

带有异步或长时间运行任务的 UndoManager

构建需要很长时间才能响应的 API 的最佳实践是啥?