1

什么是幂等性

幂等 是一个数学与计算机学概念,常见于抽象代数中。在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

以上内容取自于百度百科,我又翻墙找到一些关于幂等在Restful角度上来看的解释,如下:

From a RESTful service standpoint, for an operation (or service call) to be idempotent, clients can make that same call repeatedly while producing the same result. In other words, making multiple identical requests has the same effect as making a single request. Note that while idempotent operations produce the same result on the server (no side effects), the response itself may not be the same (e.g. a resource's state may change between requests).
[从RESTful服务的角度来看,要使操作(或服务调用)成为幂等,客户端可以重复进行相同的调用,同时产生相同的结果。换句话说,发出多个相同的请求与发出单个请求具有相同的效果。请注意,尽管幂等操作在服务器上产生相同的结果(没有副作用),但是响应本身可能是不同的(例如,资源的状态可能在请求之间发生变化)。]

可以看出,幂等接口的设计强调的是在多次相同的请求对服务器上的资源产生相同的效果,不会产生任何副作用,但响应本身可以是不相同的
举个栗子
比如一个GET请求,你在两个不同的时刻进行请求,获取资源的状态可能在两个请求之间发生了一些变化,但两个请求参数都是相同的,那么可能会获取不同的响应结果,但它本身并不对服务器上的资源有任何副作用,所以我们可以认为GET请求是天然幂等的。那么同理,DELETE请求也是天然幂等的,因为在每个DELETE请求调用之后,服务器上的状态是相同的,但是响应是不同的。

非幂等场景

业务开发中的会造成非幂等场景主要有以下几种:

  • MQ消息重复消费;
  • 创建业务订单时,用户多次点击提交按钮,生成多个业务订单;
  • 第三方支付回调时,第三方因种种原因没有收到回复导致多次回调;

上述几种非幂等场景,个人理解也是有区别的,例如用户多次点击提交按钮造成生成多个业务订单,此类场景的解决方案多为防止重复提交,防重本质是防止一个相同的请求被当成多个不同的请求来处理;而支付回调和MQ消息重复消费是第一次请求不知道结果(比如超时)或者失败的异常情况下,发起多次请求,目的是多次确认第一次请求成功,却不会因多次请求而出现多次的状态变化。
个人从 请求时间间隔 角度上去理解,多次点击按钮造成的多个业务订单多是在短时间内并发请求;而MQ消息重复消费和支付回调多是在两次相同请求之间存一定的间隔。
如下是支付宝支付成功的回调通知示例:

程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);

如何保证幂等

如何保证幂等首先先决考虑的是 幂等依据(幂等条件),换句话说就是你如何判断进来的多个请求是相同的。这个幂等依据可以是每次交互生成的token,客户端的序列号或唯一的业务订单号等等。
非幂等产生的副作用是什么呢?这个基于业务去定义,比如重复点击生成多个订单,业务状态的错误变更等。

关于非幂等产生的副作用可以仔细参考一下 分布式幂等问题解决方案三部曲 中的第二部曲。

基于上述两种有一些差异的非幂等场景,我们可以分别来讨论下不同非幂等场景下的处理方式。

并发请求造成的非幂等

此类场景下的处理方案正如上文所提到的防止重复提交,如何防止重复提交网上的文章大抵相似,有如下几种方案可作参考。

唯一索引

若为新增接口,以某个字段作为唯一索引,并发新增时只有一个新增成功。当报出唯一索引错误时,抓住异常重新查询一次即可。但如果是分库分表场景下,路由规则要保证相同请求落在同一个库的同一个表,否则该方案就会失效。

try {
    dao.insert(entity);    
    // do business
} catch (DuplicateKeyException e) {
    dao.select(param); // 幂等返回
}

乐观锁

若为更新接口,状态字段或版本字段作为幂等依据,将字段作为更新条件。

//会造成ABA问题 不过对于余额型计算来说不影响
update table_xxx set aomunt = #new_amount# where aomunt = #old_amount# 
//解决ABA问题 将值对比升级为版本号即可
update table_xxx set aomunt = #new_amount#, version = version + 1 where version = #old_version#
注意:乐观锁的更新操作,最好用主键或者唯一索引来更新,这样是行锁,否则更新时会锁表
update table_xxx set aomunt = #new_amount#, version = version + 1  where id = #id# and version = #old_version#

token机制

token机制的解决方案如图所示:

token令牌作为幂等依据,该方案的主要缺点就是需要2次请求,先要获取token令牌,然后再进行真实的业务请求。(若是单机环境,缓存可用jvm级缓存。)

分布式锁

分布式锁的解决方案如图所示:

上图中订单号作为幂等依据(根据业务不同可选不同的标志作为幂等依据),该方案在执行前必须获取对应的锁,否则不处理。也就是同一单位时间内,只有一个流程能够执行成功。

串行请求造成的非幂等

状态机

有限状态机(finite-state machine)又称有限状态自动机(finite-state automation),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。

从业务角度上来看,一个业务的生命周期存在不同的业务状态,用状态机来控制业务流程中的状态转换是个不错的选择。以下单为例,订单的状态存在未付款、已付款、待退款、已退款、待发货、已发货、已签收。(真实下单可能还存在更多的状态,此处仅仅做个示例)。
image.png
可以从图中看到,只有当待支付时才能更新为已支付,只有当已支付时才能更新为待发货,支付的整个状态变换是设计不可逆的。

使用状态机做校验的一般处理步骤先在处理前判断当前状态是否符合预期状态,执行完业务逻辑之后,变更状态还会使用乐观锁的方式进行兜底校验。

总结


Ekko
2 声望0 粉丝