首先蜗牛和大家从以下几个方面好好剖析一下接口幂等吧。
什么是接口幂等
比较专业的术语:其任意多次执行所产生的影响均与第一次执行的影响相同。
也就是多次调用的情况下,接口最终得到的结果是一致的。
那么为什么需要幂等呢?
那么哪些接口需要做幂等呢?
接口幂等的实现某种意义上是要消耗系统性能的,我们没有必要针对所有业务接口都加上幂等。
既然我们说幂等就是多次调用,接口最终得到结果一致,那么很显然,查询接口肯定是不要加幂等的,另外一些简单删除数据的接口,无论是逻辑删除还是物理删除,看场景的情况下其实也不用加幂等。
但是大部分涉及到多表更新行为的接口,咱们最好还是得加上幂等。
接口幂等实战方案
前端防抖处理
前端防抖主要可以有两种方案,一种是技术层面的,一种是产品层面的:
-
技术层面:例如提交控制在100ms内,同一个用户最多只能做一次订单提交的操作。
-
产品层面:当然用户点击提交之后,按钮直接置灰。
基于数据库唯一索引
-
利用数据库唯一索引。
-
我们具体来看一下流程,如下:
过程描述:
数据库乐观锁实现
什么是乐观锁,它总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,只在更新的时候会判断一下在此期间别人有没有去更新这个数据。
例如提交订单的进行支付扣款的时候,本来可能更新账户金额扣款的动作是这样的:
update Account set balance = balance-#{payAmount} where accountCode = #{accountCode}
加上版本号之后,咱们的代码就是这样的。
update Account set balance = balance-#{payAmount},version=version +1 where accountCode = #{accountCode} and version = #{currVersion}
这种情况下其实就要求客户端每次在请求支付下单的时候都需要上层客户端指定好当前的版本信息。
数据库悲观锁实现
悲观锁的话具有强烈的独占和排他特性。
所以我们就用select ... for update这样的语法进行行锁,当然蜗牛觉得单纯的select ... for update只能解决同一时刻大并发的幂等,所以要保证单号重试这样非并发的幂等请求还是得去校验当前数据的状态才行。
悲观锁
begin; # 1.开始事务
select * from order where order_code='666' for update # 查询订单,判断状态,锁住这条记录
if(status !=处理中){//非处理中状态,直接返回;return ;
}
## 处理业务逻辑
update order set status='完成' where order_code='666' # 更新完成
update stock set num = num - 1 where spu='xxx' # 库存更新
commit; # 5.提交事务
这边想要强调的是在校验的时候还是得带上本身的业务状态去做校验,select ... for update并非万能幂等。
后端生成token
这个方案的本质其实是引入了令牌桶的机制,当提交订单的时候,前端优先会调用后端接口获取一个token,token是由后端发放的。
当然token的生成方式有很多种,例如定时刷新令牌桶,或者定时生成令牌并放到令牌池中,当然目的只有一个就是保住token的唯一性即可。
生成token之后将token放到redis中,当然需要给token设置一个失效时间,超时的token也会被删除。
当后端接收到订单提交的请求的时候,会先判断token在缓存中是否存在,第一次请求的时候,token一定存在,也会正常返回结果,但是第二次携带同一个token的时候被拒绝了。
流程如下:
token机制
有个注意点大家可以思考一下:如果用户用程序恶意刷单,同一个token发起了多次请求怎么办?
想要实现这个功能,就需要借助分布式锁以及Lua脚本了,分布式锁可以保证同一个token不能有多个请求同时过来访问,lua脚本保证从redis中获取令牌->比对令牌->生成单号->删除令牌这一系列行为的原子性。
分布式锁+状态机(订单状态)
现在很多的业务服务都是分布式系统,
当然和上述的数据库悲观锁类似,咱们的分布式锁也只能保证同一个订单在同一时间的处理。
其次也是要去校订单的状态,防止其重复支付的,也就是说,只要支付的订单进入后端,都要将原先的订单修改为支付中,防止后续支付中断之后的重复支付。
在上述小猫的流程中还没有涉及到现金补充,如果涉及到现金补充的话,例如对接了微信或者支付宝的情况,还需要根据最终的支付回调结果来最终将订单状态进行流转成支付完成或者是支付失败。
总结
在我们日常的开发中,在一些重要的接口上,尤其是资金相关的接口上,幂等真的是相当的重要。小伙伴们,你们觉得呢?如果大家还有好的解决方案,或者有其他思考或者意见也欢迎大家的留言。
最后说一句(求关注!别白嫖!)
如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。
关注公众号:woniuxgg,在公众号中回复:笔记 就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!