9

从我看到的大量文章和商业 API 中,大多数人通过要求客户端提供 requestId 或 idempotent-key(例如https://www.masteringmodernpayments.com/blog/idempotent-stripe-requests)和基本上将 requestId <-> 响应映射存储在存储中。因此,如果有一个请求已经在这个映射中,应用程序将只返回存储的响应。

这对我来说一切都很好,但我的问题是如何处理在第一个电话仍在进行时第二个电话打进来的情况?

所以这是我的问题

  1. 我想理想的行为是第二​​个调用一直等到第一个调用完成并返回第一个调用的响应?人家是这样的吗?

  2. 如果是,第二个呼叫应该等待第一个呼叫完成多长时间?

  3. 如果第二个调用有等待时间限制,而第一个调用仍未完成,它应该告诉客户端什么?它是否应该不返回任何响应,以便客户端超时并重试?

4

2 回答 2

8

对于 wunderlist,我们使用数据库约束来确保没有请求 id(这是我们每个表中的一列)被使用过两次。由于我们的数据库技术(postgres)保证不可能插入两条违反此约束的记录,因此我们只需对潜在的插入错误做出适当的反应。基本上,我们将此细节外包给我们的数据存储。

我建议,无论您如何处理,尽量不要在您的应用程序中进行协调。如果您尝试知道是否同时发生两件事,那么很有可能会出现错误。相反,可能有一个您已经使用的系统可以提供您需要的保证。

现在,具体解决您的三个问题:

  1. 对我们来说,由于我们使用数据库约束,数据库会处理让事情排队等待。这就是为什么我个人更喜欢旧的 SQL 数据库——不是为了 SQL 或关系,而是因为它们非常擅长锁定和排队。我们将 SQL 数据库用作无连接的哑表。
  2. 这在很大程度上取决于您的系统。我们尝试将每个系统和子系统中的所有超时调整为大约 1 秒。我们宁愿快速失败也不愿排队。您可以测量然后查看您的第 99 个百分位的时间,如果您不提前知道,只需将其设置为您的超时。
  3. 我们将向客户端返回一个 504 http 状态(和适当的响应正文)。拥有幂等密钥的原因是客户端可以重试请求 - 所以我们从不担心超时并让他们这样做。同样,我们宁愿快速超时并解决问题,而不是让事情排队。如果事情排队,那么即使在某些事情得到解决之后,也必须等待一段时间才能让事情变得更好。
于 2015-08-11T15:01:02.257 回答
1

很难理解第二次调用是来自具有相同请求令牌的同一个客户端,还是来自不同的客户端。

通常,在来自不同客户端的并发请求在同一资源上运行的情况下,您还希望在请求令牌旁边实现版本控制策略以实现幂等性。

关系数据库中的典型版本策略可能是带有触发器的版本列,该触发器在每次更新记录时自动增加数字。

有了这个,所有客户端都必须指定他们的请求令牌以及他们正在更新的版本(通常使用 IfMatch 标头,版本号用作 ETag 的值)。

在服务器端,当需要更新资源的状态时,您首先检查数据库中的版本号是否与 ETag 中提供的版本相匹配。如果他们这样做,您编写更改并增加版本。假设第二个请求与第一个请求在相同的版本号上运行,那么它将失败并返回 412(或 409,具体取决于您如何解释 HTTP 规范),并且客户端不应重试。

如果您真的想在第一个请求正在进行时立即停止第二个请求,那么您正在走悲观锁定的路线,这不太适合 REST API。

如果您实际上是在谈论客户端因为收到短暂的网络错误而使用相同的请求令牌重试,情况几乎相同。

两个请求将同时运行,第二个请求将开始,因为第一个请求还没有完成,还没有将请求令牌记录到数据库中,但无论哪个最终完成,都会成功并记录请求令牌。

对于另一个请求,它将收到版本冲突(因为第一个请求已经增加了版本),此时它应该重新检查请求令牌数据库表,在那里找到它自己的令牌并假设它是一个在之前完成的并发请求它做到了并返回200。

看起来很多,但是如果您想在处理 REST、幂等性和并发性时涵盖所有奇怪而奇妙的故障模式,这就是处理它的方法。

于 2017-08-22T12:55:37.023 回答