所谓幂等性设计,就是说,一次和多次请求某一个资源应该具有同样的副作用。用数学的语言来表达就是:f(x) = f(f(x))。 在数学里,幂等有两种主要的定义。 在某二元运算下,幂等元素是指被自己重复运算(或对于函数是为复合)的结果等于它自己的元素。 某一元运算为幂等的时,其作用在任一元素两次后会和其作用一次的结果相同。
所谓幂等性设计,就是说,一次和多次请求某一个资源应该具有同样的副作用。用数学的语言来表达就是:f(x) = f(f(x))。
下面是维基百科中对于幂等的定义:
在数学里,幂等有两种主要的定义。 在某二元运算下,幂等元素是指被自己重复运算(或对于函数是为复合)的结果等于它自己的元素。 某一元运算为幂等的时,其作用在任一元素两次后会和其作用一次的结果相同。
百度百科上是这么说的:
在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。 幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。
简而言之,幂等是指:多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。
根据上面对幂等性的定义我们得知:产生重复数据或数据不一致,这个绝大部分是由于发生了重复请求。
这里的重复请求是指同一个请求在一些情况下被多次发起。
在接口调用时一般情况下都能正常返回信息不会重复提交,不过在遇见以下情况时可能就会出现问题,如:
举几个支付时的例子:
因为系统超时,而调用户方重试一下,会给我们的系统带来不一致的副作用。
幂等性主要保证多次调用对资源的影响是一致的。其本质是通过唯一标识,标记同一操作的方式,来消除多次执行的副作用。
下一用 SQL DML 命令来理解幂等性操作:
总的来说,我们解决幂等性问题就是要控制对资源的写操作,因此我们可以通过控制重复请求、过滤重复动作、解决重复写风险三种方式分别在源头、过程以及结果上对幂等性问题进行分析解决。
通过前端防重保证幂等是最简单的实现方式,前端相关属性和JS代码即可完成设置。可靠性并不好,有经验的人员可以通过工具跳过页面仍能重复提交。主要适用于表单重复提交或按钮重复点击。
主要解决方案**:**
# PRG 模式(前端)
PRG 模式即 POST-REDIRECT-GET。当用户进行表单提交时,会重定向到另外一个提交成功页面,而不是停留在原先的表单页面。这样就避免了用户刷新导致重复提交。同时防止了通过浏览器按钮前进/后退导致表单重复提交。 是一种比较常见的前端防重策略。
# 分布式锁
利用 Redis 记录当前处理的业务标识,当检测到没有此任务在处理中,就进入处理,否则判为重复请求,可做过滤处理。
订单发起支付时,支付系统先去 Redis 中查询是否存在该订单号的 Key。若不存在,则在 Redis 中新增这个Key。 接着查询订单是否已支付,若未支付,则支付完成后删除该订单的Key。 通过Redis做分布式锁,只有当一个请求执行完成后,才能执行下个请求。
# Token 机制实现
通过 Token 机制实现接口的幂等性,这是一种比较通用性的实现方法。
具体流程步骤:
# 防重表
对于防止数据重复提交,还有一种解决方案就是通过防重表实现。防重表的实现思路也非常简单。首先创建一张表 作为防重表,同时在该表中建立一个或多个字段的唯一索引作为防重字段,用于保证并发情况下,数据只有一条。 在向业务表中插入数据之前先向防重表插入,如果插入失败则表示是重复数据。
常见的方式有:悲观锁(for update)、乐观锁、唯一约束。
# 悲观锁
假设每一次拿数据,都有认为会被修改,所以给数据库的行或表上锁。
当数据库执行 select for update
时会获取被 select 中的数据行的行锁,因此其他并发执行的 select for update
如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。
# 乐观锁
就是很乐观,每次去拿数据的时候都认为别人不会修改。更新时如果 version 变化了,更新不会成功。
缺点:就是在操作业务前,需要先查询出当前的 version 版本。
另外,还存在一种:状态机控制
例如:支付状态流转流程:待支付 -> 支付中 -> 已支付
具有一定要的前置要求的,严格来讲,也属于乐观锁的一种。
# 唯一约束
这种实现方式是利用 mysql 唯一索引的特性。