如何在同时接收具有相同ID的多个请求时保持API幂等?

时间:2015-07-25 01:36:25

标签: api rest http idempotent

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

这对我来说都不错,但我的问题是如何处理第一次通话仍在进行中第二次来电的情况?

所以这是我的问题

  1. 我想理想的行为是第二​​次通话一直等到第一次通话结束并返回第一个通话的响应?人们这样做是怎么回事?

  2. 如果是,第二次通话等待第一次通话结束需要多长时间?

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

2 个答案:

答案 0 :(得分:5)

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

我建议,无论你怎么做,都要尽量不要在你的应用程序中进行协调。如果您尝试知道如果一次发生两件事情,那么很可能会出现错误。相反,您可能已经使用了一个可以提供所需保证的系统。

现在,专门解决你的三个问题:

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

答案 1 :(得分:0)

如果第二个呼叫来自具有相同请求令牌的同一客户端或不同的客户端,则有点难以理解。

通常,在来自同一资源的不同客户端的并发请求的情况下,您还需要实现版本控制策略以及幂等的请求令牌。

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

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

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

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

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

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

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

它似乎很多,但是如果你想要在处理REST,幂等性和并发性时想要涵盖所有奇怪和奇妙的失败模式,这就是处理它的方法。