我们正在实现一个REST API,它将启动多个长时间运行的后端任务。我一直在阅读RESTful Web Services Cookbook,其中的建议是返回HTTP 202/Accepted,并带有指向正在处理的任务的Content-Location头。(例如http://www.example.org/orders/tasks/1234),并让客户端轮询此URI以获取长时间运行任务的更新。
这个想法是让REST API立即将消息发送到队列,后台worker角色从队列中获取消息并旋转多个后端任务,也使用队列。我认为这种方法的问题在于如何为任务分配唯一的ID,然后让客户机通过向Content-Location URI发出GET请求任务的状态。
如果REST API立即发送到队列,那么它可以生成GUID并将其作为添加到队列的消息的属性附加,但是获取请求的状态变得很尴尬。
另一种选择是让REST API立即向数据库添加一个条目(假设是一个带有新订单id的订单)和一个初始状态,然后在队列中放置一条消息以启动后台任务,然后更新该数据库记录。API将在Content-Location报头的URI中返回这个新订单ID,供客户端在检查任务状态时使用。
以某种方式先添加数据库条目,然后将消息添加到队列中似乎是向后的,但仅将请求添加到队列使得很难跟踪进度。
推荐的方法是什么?
非常感谢你的见解。
我假设您的系统如下所示。您有一个REST服务,它接收来自客户机的请求。它将请求转换为业务逻辑可以理解的命令。将这些命令放入一个队列中。您有一个或多个工作者可以处理和从队列中删除这些命令,并将结果发送给REST服务,后者可以响应客户端。
您的问题是,由于您的长时间运行任务,客户端连接超时,因此您无法发送响应。因此,您可以做的是在将命令放入队列并添加轮询链接之后发送可接受的202,以便客户机能够轮询更改。你的任务有多个子任务,所以有进度,而不仅仅是挂起和完成状态的变化。
- 如果您想坚持轮询,您应该创建一个新的REST资源,其中包含长时间运行任务的实际状态和进度。这意味着您必须将此信息存储在数据库中,以便REST服务能够响应像
GET /tasks/23461/status
这样的请求。这意味着你的worker必须在完成一个子任务或整个任务时更新数据库。 - 如果您的REST服务作为守护进程运行,那么您可以通过进度通知它,因此在数据库中存储任务状态将不是worker的责任。这种REST服务也可以在内存中存储信息。 如果你决定使用websockets来通知客户端,那么你可以创建一个通知服务。通过REST,您必须使用任务id进行响应。之后,您在websocket连接上发回此任务id,这样通知服务将知道哪个websocket连接订阅了某个任务的事件。在此之后,你不需要REST服务,你可以通过websocket连接发送进度,只要客户端不关闭连接。
- 您可以通过以下方式组合这些解决方案。您允许REST服务创建任务资源,因此您将能够通过使用轮询链接访问进度。之后,您通过websockets连接发送回带有202的标识符。所以你可以使用通知服务来通知客户端。在此过程中,worker将通知REST服务,REST服务将创建一个类似
GET /tasks/23461/status
的链接,并通过通知服务将该链接发送给客户端。之后,客户端可以使用该链接来更新其状态。
我认为最后一个是最好的解决方案,如果您的REST服务作为守护进程运行。这是因为您可以将通知责任转移到专用的通知服务,该服务可以使用websockets,轮询,SSE,任何您想要的。它可以在不杀死REST服务的情况下崩溃,因此REST服务将保持稳定和快速。如果您也发送一个带有202的手动更新链接,那么客户机就可以进行手动更新(假设是人工控制的客户机),因此如果通知服务不可用,您就会出现类似于正常降级的情况。您不必维护通知服务,因为它对任务一无所知,它只会向客户机发送数据。你的员工不需要知道如何发送通知和如何创建超链接。维护客户端代码也会更容易,因为它几乎是一个纯REST客户端。唯一的额外功能是订阅通知链接,它不会经常更改。