又是每个程序员都应该知道的:幂等性


在编程世界中,每个开发人员都应该理解许多概念,以便构建高效可靠的系统。其中一个重要的概念是幂等性,它指的是操作或函数的属性,多次应用时产生的结果与仅应用一次时产生的结果相同。这似乎是一个简单的概念,但它对于构建分布式系统具有重要意义。

无论您是初学者还是经验丰富的开发人员,了解幂等性都是一项基本技能,它将帮助您构建更强大、更可靠的系统。

比方说,我们有一个食品订购应用程序,为了使我们的应用程序保持简单,我们有两个基本服务:发货和订单。

  • 当客户下订单时,首先创建订单,然后创建发货说明。
  • 如果所有交易都成功,就会向客户发送通知。

即使在这种简单的情况下,也会发生许多故障。

这些故障可能来自各种因素。它们包括服务器、网络、负载平衡器、软件、操作系统,甚至是系统操作员的失误。

例如,即使订单和发货服务工作正常,但如果在返回响应时由于网络延迟而导致客户无法收到响应,会发生什么情况呢?当然,在这种情况下,我们首先想到的是使用超时、重试和回退等模式。

但是,如果我们在这种情况下再次尝试服务调用呢?我们是否需要重新排序?重试请求可能会导致多个订单,后果非常严重。

这就是为什么设计幂等应用程序接口(Idempotent API)非常重要的原因。

HTTP 方法的幂等性
HTTP(超文本传输​​协议)是一种广泛用于 Web 服务器和客户端之间通信的协议。幂等性在 HTTP 方法的设计中起着重要作用,HTTP 方法用于定义客户端想要对资源执行的操作类型。在本节中,我们将探讨幂等性如何应用于某些 HTTP 方法。

  1. GET方法:GET方法用于从服务器检索资源。它是一种幂等方法,因为多次检索同一资源不会更改资源本身或对服务器造成任何副作用。
  2. PUT方法:PUT方法用于更新服务器上的资源。它是幂等的,因为多次发送相同的请求将导致相同的资源状态,就像该请求仅发送一次一样。例如,如果您多次发送PUT请求以使用同一新电子邮件地址更新用户的电子邮件地址,则该用户的电子邮件地址只会更新一次。
  3. DELETE方法:DELETE方法用于从服务器删除资源。它是幂等的,因为多次删除资源与仅删除一次具有相同的结果。如果资源已被删除,则对同一资源发送DELETE请求不会导致任何更改。
  4. POST方法:POST方法用于在服务器上创建新资源或提交要处理的数据。它不是幂等的,因为多次发送相同的请求会创建多个资源或多次提交相同的数据,从而导致不同的结果。

如何在 POST 方法中实现惰性?
在一个分布式系统中,有许多客户端进行许多调用,并且有许多请求在飞行中,我们面临的挑战是如何识别一个请求是否是以前某个请求的重复请求?

很简单,在请求体或请求头中加入一个唯一标识符,就可以使 POST 请求具有惰性,从而用于识别和防止重复请求。

有许多方法可以用来确定一个请求是否是先前请求的副本。例如,可以根据请求中的参数推导出一个合成标记。你可以推导出一个现有参数的哈希值,并假定来自同一调用者、具有相同参数的任何请求都是重复请求。从表面上看,这似乎可以简化客户体验和服务实施。任何与之前请求一模一样的请求都会被视为重复请求。但是,这种方法不可能在所有情况下都有效。

例如,假设您点了一份餐,而您隔壁的邻居也点了同样的餐,那么这些请求是重复请求还是两个不同的请求呢?
或者在你下单后,你的朋友打电话来说他饿了,过了不久你又重新创建了同样的订单,我们是否会将它们视为重新提出的请求?

这种情况是否与我们刚才提到的由于网络延迟而导致客户端重试服务的情况非常相似?有可能呼叫者实际上想要两份相同的餐点。

一般首选的方法是在 API 合同中包含一个由调用者提供的唯一客户端请求标识符。来自同一调用者、具有相同客户请求标识符的请求可视为重复请求,并进行相应处理。由调用者提供的唯一客户请求标识符可以满足这一需求。

客户请求创建一个资源,该资源会显示一个唯一的客户请求标识符。服务接收到请求后,首先会检查自己以前是否见过这个标识符。如果没有,则开始处理请求。服务会根据客户标识符及其唯一客户请求标识符为该请求创建并存储一个幂等 "会话"。如果收到来自同一客户的具有相同唯一客户请求标识符的后续请求,服务就会知道它已经看到过该请求,并可采取适当措施。

这里有两个要点:

  1. 一个是唯一客户请求标识符将存储多长时间,
  2. 另一个是如果交易不成功,则不应创建唯一客户请求标识符,

也就是说,这应该是一个 ACID 事务。

banq注:POST是一个天然的达成共识的非幂等性,如果你非得设计成幂等性,反而过度设计,弄巧成拙,引入事务增加复杂性和失败的概率。只要提供用户能够删除重复记录的功能即可,由用户判断选择。网络默认是不可靠的,不确定的,试图在某个上下文让其变得可靠,必然遭遇CAP定理 的权衡,拿了芝麻丢了西瓜。