幂等性
概念
在计算机科学中,依其应用的语境不同,幂等性一词可能有不同的含义:
在命令式编程中,如果在一个或几个调用之后系统状态保持不变,则带有副作用的子程序是幂等的,换句话说,如果从系统状态空间到与子程序相关联的自身的函数在定义中给出的数学意义上是幂等的;
在函数式编程中,如果一个纯函数在定义中给出的数学意义上是幂等的,那么它就是幂等的。
在许多情况下,这是一个非常有用的属性,因为它意味着操作可以在不造成意外影响的情况下尽可能频繁地重复或重试。
对于非幂等操作,算法可能需要跟踪操作是否已经执行。
- 个人比较赞同的解释
幂等性是系统的接口对外一种承诺(而不是实现), 承诺只要调用接口成功, 外部多次调用对系统的影响是一致的. 声明为幂等的接口会认为外部调用失败是常态, 并且失败之后必然会有重试.
RESTful 中的意义
从基于rest的服务的角度来看,对于幂等的操作(或服务调用),客户端可以重复地进行相同的调用,同时产生相同的结果。
换句话说,发出多个相同的请求与发出单个请求的效果相同。注意,虽然幂等操作在服务器上产生了相同的结果(没有副作用),但是响应本身可能不是相同的(例如,请求之间资源的状态可能会改变)。
将PUT和DELETE方法定义为幂等。然而,有一个关于删除的警告。如果删除成功,通常会返回一个200 (OK)或204(无内容),但在后续调用中通常会返回一个404(未找到),除非服务配置为“标记”资源以便删除,而不实际删除它们。然而,当服务实际删除资源时,下一个调用将不会找到删除资源并返回404。但是,每次删除调用之后服务器上的状态都是相同的,但是响应是不同的。
GET、HEAD、选项和跟踪方法被定义为安全的,这意味着它们仅用于检索数据。这也使它们具有幂等性,因为多个相同的请求的行为是相同的。
方法的安全性和幂等性
安全性
安全方法是不修改资源的HTTP方法。
例如,在资源URL上使用GET或HEAD,应该永远不会更改资源。
然而,这并不是完全正确的。它的意思是:它不会改变资源表示。仍然有可能,安全方法确实会改变服务器或资源上的东西,但这不应该以不同的表示形式反映出来。
这意味着以下内容是不正确的,如果这将实际删除博文:
GET /blog/1234/delete HTTP/1.1
安全方法是可以缓存的方法,可以预先获取,不会对资源产生任何影响。
幂等性
幂等HTTP方法是一种HTTP方法,可以多次调用,但结果相同。
如果只调用该方法一次或十次,则没有关系。结果应该是一样的。同样,这只适用于结果,而不适用于资源本身。这仍然可以操作(就像更新-时间戳一样,只要这些信息没有在(当前)资源表示中共享。
考虑下面的例子:
a = 4; //(1)
a++; //(2)
第一个例子是幂等的:无论我们执行这个语句多少次,a总是4。
第二个例子不是幂等的。执行10次将得到与运行5次不同的结果。因为两个例子都在改变a的值,所以它们都不是安全的方法。
作用
幂等性在构建容错API时非常重要。
假设客户希望通过POST更新资源。由于POST不是幂等的方法,多次调用它可能导致错误的更新。
如果您将POST请求发送到服务器,但您得到了超时,会发生什么情况?资源是否更新了?
超时是在向服务器发送请求时发生的,还是在向客户端发送响应时发生的?
我们能够安全地再次重试吗?或者我们需要首先弄清楚资源发生了什么?
通过使用幂等方法,我们不必回答这个问题,但是我们可以安全地重新发送请求,直到从服务器得到响应为止。
在处理安全方法时也要小心:
如果像GET这样看似安全的方法将更改资源,那么您和服务器之间的任何中间件客户机代理系统都可能缓存此响应。
另一个想通过相同URL(如http://example.org/api/article/1234/delete)更改此资源的客户端不会调用服务器,而是直接从缓存返回信息。
非安全(非幂等)方法永远不会被任何中间件代理缓存。
应用场景
MVCC
多版本并发控制,乐观锁的一种实现,在数据更新时需要去比较持有数据的版本号,版本号不一致的操作无法成功。
例如博客点赞次数自动+1的接口:
update blogTable set count= count+1,version=version+1 where id=321 and version=123
每一个 version 只有一次执行成功的机会,一旦失败必须重新获取。
去重表
利用数据库表单的特性来实现幂等,常用的一个思路是在表上构建唯一性索引,保证某一类数据一旦执行完毕,后续同样的请求再也无法成功写入。
例子还是上述的博客点赞问题,要想防止一个人重复点赞,可以设计一张表,将博客id与用户id绑定建立唯一索引,每当用户点赞时就往表中写入一条数据,这样重复点赞的数据就无法写入。
TOKEN机制
这种机制就比较重要了,适用范围较广,有多种不同的实现方式。其核心思想是为每一次操作生成一个唯一性的凭证,也就是token。
一个token在操作的每一个阶段只有一次执行权,一旦执行成功则保存执行结果。对重复的请求,返回同一个结果。
以电商平台为例子,电商平台上的订单id就是最适合的token。当用户下单时,会经历多个环节,比如生成订单,减库存,减优惠券等等。
每一个环节执行时都先检测一下该订单id是否已经执行过这一步骤,对未执行的请求,执行操作并缓存结果,而对已经执行过的id,则直接返回之前的执行结果,不做任何操作。
这样可以在最大程度上避免操作的重复执行问题,缓存起来的执行结果也能用于事务的控制等。
参考资料
https://en.wikipedia.org/wiki/Idempotence
https://blog.jonathanoliver.com/idempotency-patterns/
- rest
https://www.restapitutorial.com/lessons/idempotency.html
http://www.infoq.com/cn/news/2013/05/idempotent
https://juejin.im/entry/57fec6850e3dd90057e1e47e
- usage
https://www.jianshu.com/p/475589f5cd7b
https://cloud.tencent.com/developer/article/1116244