我遇到了对(任何)一个API端点(ASP.NET Web API 2)的并发请求的问题。当第二个请求在第一个请求完成之前开始处理时,数据库实体可能会出现并发问题。
例:
1:请求R1开始被处理,它将实体E1和E2读入存储器
2:请求R2开始被处理,它将实体E2和E1读入存储器
3:请求R1更新对象E1
4:如果E1和E2已更新,则请求R1创建E3
5:请求R2更新对象E2
6:如果已更新E1和E2,则请求R2创建E3
问题是两个请求在另一个请求更新之前读取实体E1和E2。因此,两者都看到过时的,未更新的版本,而E3从未创建过。
所有请求都在单个数据库事务中处理(隔离级别"读取已提交",SQL Server 2017上的实体框架6)。 根据我目前的理解,我不认为问题可以在数据访问/数据存储级别上解决(例如,更严格的数据库隔离级别或乐观锁定),但需要在代码层次结构(请求级别)中更高。
我的建议是在输入API方法时悲观地锁定可以更新的所有实体。当另一个API请求启动时,需要对已经锁定的任何权限进行写锁定,它需要等到所有先前的锁被释放(队列)。请求通常在不到250毫秒的时间内处理
这可以使用装饰API方法的自定义属性来实现:[LockEntity(typeof(ExampleEntity), exampleEntityId)]
。可以使用零到多个这些属性来修饰单个API方法。如果可能,锁定机制将与async / await一起使用,或者只是让线程进入休眠状态。 锁定同步需要跨多个服务器工作,因此我在此处看到的最简单的选项是应用程序数据存储中的表示(单个,全局)。
这种方法很复杂,并且存在一些性能缺点(阻塞请求处理,在锁定处理期间执行数据库查询),因此我对任何建议或其他想法持开放态度。
我的问题:
1.有没有描述这个问题的术语?这看起来很普通,但到目前为止我还不知道
2.有没有关于如何解决这个问题的最佳做法?
3.如果没有,我的建议是否有意义?
答案 0 :(得分:1)
我不会破解Web和REST的无状态并且不会阻止请求。 250毫秒是四分之一秒,这是相当慢的IMO。例如,100个并发请求将相互阻塞,并且至少有一个流将等待25秒!这会大大减慢应用程序的速度。
我看到一些应用程序(包括Atlassians Confluence)通知用户,当前数据集同时由另一个用户更改。 (有时也提到了其他用户。)我会以相同的方式使用Websockets,例如SignalR。其他应用程序,例如service-now,正在将远程更改合并到当前打开的数据集中。
如果您不想使用Websockets,您还可以尝试合并或检查服务器端,并通过特定的HTTP状态代码和消息通知用户。告诉用户同时改变了什么,尝试合并并询问合并是否正常。