如何在RESTful服务中处理互斥操作
本文关键字:处理 操作 服务 RESTful | 更新日期: 2023-09-27 17:59:29
我现在正在两个WebApi项目中工作,在这两个项目中我们都遇到了相同的问题:如果正在执行某个操作A
,则我们有一些操作Actions
无法执行,反之亦然。因此,例如,如果这些Actions
中的任何一个现在正在运行,并且用户向操作A
发送请求,则操作A
必须等待其他操作完成才能运行。
来自有状态服务背景,我们的第一个想法是使用锁,尽管有很多阻力。我们创建了一个单例服务,它只存储映射到用户Id的object
,然后可以将其用作锁。
当然,这会带来一些非常棘手的问题,比如:
(1) 我们必须强制用户始终只与一个服务器实例对话,即使他们的请求来自世界不同地区的不同设备,因为锁不会自动从一个实例跨到另一个实例。
(2) 然后,我们将不得不构建自己的[次优]负载均衡器。我们的计划是使用Azure的内置软件。
因此,我的问题是:如何优雅地处理这个问题,使不会损害水平可伸缩性,并且最好不需要自定义负载平衡策略?
一种方法(当然不是唯一的方法)是对请求进行排队并用202(Accepted)进行响应。通常,响应还将提供一个URL,然后客户端可以监视该URL以确定未完成请求的状态。类似地,如果需要取消挂起的操作,客户端可以删除该URL。
编辑:换言之,假设每个修改共享资源的请求都被处理如下:
- 客户提出请求
- 前端服务器将请求转发到后端服务器,在那里对请求进行排队处理(无需锁定)。除了明显的拒绝(格式错误的请求、未经授权的访问等)之外,前端服务器总是接受请求。对于前端服务器来说,实际请求是否成功或何时实际处理请求并不重要
(现在,假设客户端实际上需要知道请求的结果…)
- 前端服务器返回一个202(接受)响应,其中包括一个URL。URL有效地指向关联请求的状态(可能还有结果)
- 客户端定期轮询URL。(前端服务器只是读取后端状态,所以它应该很快,不需要锁定。)
- 最终,后端服务器将处理实际请求,并根据需要更新请求状态
- 客户端获取更新后的状态并相应地采取行动
这里的关键点是:
- 大多数时候都不需要锁。当需要它们时应该只用于非常快速的操作
- 请求的实际处理在后端序列化。这避免了并发问题
- 前端的负载平衡和扩展问题被最小化,因为它们仍然只是使用后端资源。前端服务器不需要与除后端
尽管与最初的问题无关,但这种方法的另一个优点是,"排队"的请求有效地成为共享资源的事务历史。
您可以路由到任何服务器,只要它们都使用某种并发控制机制,排除并发运行关键操作。该机制可以是分布式锁(zookeeper、etcd、redis、hazelcast等)或集中式锁(例如,仅用于锁定的特定服务器、数据库、调优的memcached集群)。然后,您的服务器可以在等待锁定的同时等待对REST客户端的响应,也可以实现seairth的回答中建议的策略。我不会说这根本不会影响可扩展性,但如果关键部分很短,并且很少发生冲突,那么所有接收请求的服务器大部分时间都会很忙。