c#中并发请求api接口

    科技2026-08-29  19

    c#中并发请求api接口

    Imagine the following scenario:

    想象以下情况:

    User A requests resource 1 via a GET endpoint

    用户A通过GET端点请求资源1 User B requests resource 1 via a GET endpoint

    用户B通过GET端点请求资源1 User A makes changes on resource 1 and saves its changes via a PUT request

    用户A对资源1进行更改,并通过PUT请求保存其更改 User B makes changes on resource 1, on the same fields as user A, and saves its changes via a PUT request

    用户B在与用户A相同的字段上对资源1进行更改,并通过PUT请求保存其更改

    Since users A and B both requested the same version of resource 1, you now have a problem, because the PUT request triggered by user B erased the changes made by user A, and chances are, user A is mad and wondering why his changes do not show up anymore, even though he swears that he did not forget to save. Does that scenario sound familiar ? If so, read on.

    由于用户A和B都请求了相同版本的资源1,因此您现在遇到了问题,因为由用户B触发的PUT请求删除了用户A所做的更改,并且很可能用户A生气了,并且想知道为什么他的更改会即使他发誓他不会忘记保存,也不会再出现了。 听起来很熟悉吗? 如果是这样,请继续阅读。

    找出问题 (Identifying the problem)

    The problem is very simple: users A and B requested a resource more or less at the same time, and got the same version of this resource. Then, they both made changes to the resource in parallel, and whoever saved last erased the changes of the other.

    问题非常简单:用户A和B或多或少地同时请求了一个资源,并获得了该资源的相同版本。 然后,他们俩并行地对资源进行了更改,最后保存的人将擦除另一个资源的更改。

    There are actually 2 possible ways to solve this issue: through what’s called pessimistic locking or optimistic locking.

    解决此问题的方法实际上有两种:通过悲观锁定或乐观锁定。

    悲观与乐观锁定 (Pessimistic vs Optimistic locking)

    Pessimistic locking means that as soon as a user starts making changes on a resource, he locks this resource. While this resource is locked, no one else can edit this resource. When the user is done with his changes, he unlocks the resource so that other users can retrieve the resource and make changes to it. In other words, pessimistic locking ensures that 2 users can not make changes on one resource at the same time.

    悲观锁定意味着用户一旦开始对资源进行更改,就会锁定该资源。 锁定此资源后,没有其他人可以编辑此资源。 当用户完成更改后,他将资源解锁,以便其他用户可以检索该资源并对其进行更改。 换句话说,悲观锁定确保2个用户不能同时对一个资源进行更改。

    Optimistic locking on the other hand, allows users to retrieve a resource in parallel, however it will not allow a user to erase a previous version of a resource. If we take the example scenario above, in steps 1 and 2, users A and B will retrieve resource 1 both in, say, version 38. When user A saves resource 1, it will send to the API the information that the version being saved is version 38. The API will then retrieve the resource from the database and compare the version in database to the one provided in the API call, and if they differ, it will not accept the save operation. When user A saves version 38 of the resource, the save will be accepted and the version number will be incremented, but when user B tries to save version 38, the resource in the database will be at version 39, and therefore the save will not be accepted, preventing user B from overwriting user A’s changes.

    另一方面,乐观锁定允许用户并行检索资源,但是不允许用户擦除资源的先前版本。 如果我们以上述示例场景为例,则在步骤1和2中,用户A和B都将在版本38中检索资源1。当用户A保存资源1时,它将向API发送有关正在保存版本的信息是38版。API然后将从数据库中检索资源,并将数据库中的版本与API调用中提供的版本进行比较,如果它们不同,则它将不接受保存操作。 当用户A保存该资源的版本38时,将接受该保存,并且版本号将增加,但是当用户B尝试保存该版本38时,数据库中的资源将处于版本39,因此保存不会被接受,防止用户B覆盖用户A的更改。

    RESTful API应该实现什么? (What should be implemented in a RESTful API ?)

    So what kind of locking should you implement in your RESTful API ? Pessimistic or optimistic locking ?

    那么,您应该在RESTful API中实现哪种锁定? 悲观还是乐观锁定?

    Even though implementing pessimistic locking is not impossible, because a RESTful API is stateless, I would not recommend it, as this would mean that your API will need to rely on client applications to unlock resources automatically if users forget to do so.

    尽管实现悲观锁定不是不可能的,因为RESTful API是无状态的,但我不建议这样做,因为这意味着您的API将需要依赖客户端应用程序来自动解锁资源(如果用户忘记这样做)。

    Imagine the following scenario: user A retrieves resource 1 and the client application automatically locks the resource as user A starts making changes on it. After making some changes on resource 1 and saving them, user A closes the application and goes on vacation for 2 weeks… Unless your client application somehow automatically unlocked resource 1, resource 1 is now locked and can not be edited by anyone until user A is back from vacation… Unless you want to make your users mad at you, I do not advise you to go this way.

    设想以下情形:用户A检索资源1,并且当用户A开始对其进行更改时,客户端应用程序自动锁定该资源。 在对资源1进行一些更改并将其保存后,用户A关闭该应用程序并休假2周……除非您的客户端应用程序以某种方式自动解锁了资源1,否则资源1现在被锁定并且在用户A被锁定之前,任何人都无法对其进行编辑。从假期回来……除非您想让用户生气,否则我不建议您这样做。

    In optimistic locking however, users can not block each other from editing the same resource, so the issue created by pessimistic locking can not happen. In the scenario described in the introduction of this article, user B will simply get an error message telling him that his changes can not be saved because a new version of the resource was created by user A in the meantime. It is then up to you to decide whether you want to try to resolve conflicts between the changes made by user A and the changes made by user B automatically (which can be a nightmare) or whether you just ask user B to redo his changes after retrieving the new version of the resource.

    但是,在乐观锁定中,用户不能互相阻止编辑同一资源,因此,不会发生由悲观锁定造成的问题。 在本文引言中描述的场景中,用户B只会收到一条错误消息,告诉他由于用户A同时创建了新版本的资源,因此无法保存其更改。 然后由您决定是要尝试自动解决用户A所做的更改与用户B所做的更改之间的冲突(这可能是一场噩梦),还是只是要求用户B在更改后重做他的更改?检索资源的新版本。

    Note that, if for some reason, this “inconvenience” caused by optimistic locking is not acceptable for you, you can still implement pessimistic locking and set a lock expiration to, for example, automatically release a lock that was created 2 hours ago, and is therefore expired. Your client application will then have to reflect this expiration.

    请注意,如果由于某种原因您不能接受由乐观锁定引起的这种“不便”,您仍然可以实施悲观锁定并设置锁定有效期,例如,自动释放2小时前创建的锁定,并且因此已过期。 然后,您的客户端应用程序将必须反映此到期。

    如何实现乐观锁定 (How to implement optimistic locking)

    The standard way to implement optimistic locking in a RESTful API is using the Etags and If-Match headers. When a resource is retrieved, it is received with an Etag header, which is a string of characters identifying the version of the resource being retrieved. When a client later wants to update this resource, it has to provide the value of the Etag in the If-Match header. If the Etag provided in the If-Match header matches the version of the resource in the database, the resource can be updated, otherwise the update is rejected.

    在RESTful API中实现乐观锁定的标准方法是使用Etags和If-Match标头。 检索资源时,将使用Etag标头接收该资源,该标头是一串字符串,用于标识所检索资源的版本。 当客户端以后想要更新此资源时,它必须在If-Match标头中提供Etag的值。 如果If-Match标头中提供的Etag与数据库中资源的版本匹配,则可以更新资源,否则,更新将被拒绝。

    Note that if you are using MongoDB as a backend and Mongoose as an ORM, you already have a built-in version mechanism and do not need to implement one. Mongoose stores versions of a document in a __v attribute of a document. All you need to do is to make sure this __v attribute is incremented on every save operation. In order to do this, Mongoose provides a very convenient option: optimisticConcurrency (see documentation here), which, if set, will automatically increment this __v attribute on every save operation. Note however that it will not increment this attribute on a findOneAndUpdate operation, so you will need to do this manually:

    请注意,如果您将MongoDB用作后端,而将Mongoose用作ORM,则您已经具有内置的版本机制,无需实现。 猫鼬将文档的版本存储在文档的__v属性中。 您需要做的就是确保在每个保存操作中都增加__v属性。 为了做到这一点,Mongoose提供了一个非常方便的选项: optimisticConcurrency (请参阅此处的文档),如果设置了该__v ,它将在每次保存操作时自动增加此__v属性。 但是请注意,它不会在findOneAndUpdate操作上增加此属性,因此您将需要手动执行此操作:

    await q.findOneAndUpdate({ _id }, { $set: body, $inc: { __v: 1 }}

    You will then need to provide this __v attribute on every GET request, either directly in the response payload or in the Etag header, and your client application will then need to send you this __v attribute, again either in the request payload or in the If-Match header.

    然后,您将需要在每个GET请求中直接在响应有效负载中或在Etag标头中提供此__v属性,然后您的客户端应用程序将再次在请求有效负载中或在If-Match向您发送此__v属性If-Match标头。

    When there is a conflict (a resource can not be saved because it was edited in the meantime), your API should reply with a 412 Precondition Failed HTTP status code. It will then be up to the client application to interpret this status code and to show a comprehensible error message to the user.

    发生冲突时(由于同时对其进行了编辑,无法保存资源),您的API应使用412 Precondition Failed HTTP状态代码进行回复。 然后,由客户端应用程序来解释此状态代码并向用户显示可理解的错误消息。

    结论:使用Etag和If-Match标头为您的资源实现乐观锁定 (Conclusion: Implement Optimistic locking for your resources using the Etag and If-Match headers)

    Depending on your resources, you might not need to implement optimistic locking at all: if your resources are edited by a single user, there is no risk of concurrent requests and you will not need to worry about implementing optimistic locking.

    根据您的资源,您可能根本不需要实现乐观锁定:如果您的资源是由单个用户编辑的,则不会存在并发请求的风险,并且您不必担心实现乐观锁定。

    If however some of your resources can be updated by different users, you will, sooner or later, encounter the scenario described at the beginning of this article. In this case, my recommendation is to implement optimistic locking using the Etag and If-Match headers, returning a 412 Precondition Failed if a resource can not be updated.

    但是,如果某些资源可以由不同的用户更新,则您迟早会遇到本文开头描述的情况。 在这种情况下,我的建议是使用Etag和If-Match标头实现乐观锁定, If-Match 412 Precondition Failed更新资源,则返回412 Precondition Failed 。

    翻译自: https://medium.com/@guillaume.viguierjust/handling-concurrent-requests-in-a-restful-api-5a25a4b81a1

    c#中并发请求api接口

    相关资源:C# 并发测试
    Processed: 0.009, SQL: 9