什么是接口幂等性?
接口幂等性:用户对同一操作发起多次请求,对数据的影响是一致不变的,不会因为多次请求而产生副作用,即任意多次执行所产生的影响均与一次执行的影响相同
接口幂等性问题场景
支付场景:在订单的支付中,如果没有幂等性,接口的重试可能造成重复支付。用户购买商品后,发起支付操作,支付系统处理支付订单成功后,订单已经扣款成功,但是由于网络等原因没有及时返回给用户支付结果,相应的支付流水也都已经生成,这个时候用户又进行支付操作,进行第二次扣款,扣款成功后返回给用户。查看支付订单和流水会发现支付两次。
为什么要有幂等这种场景?
因为在大的系统中,都是分布式部署,如订单业务和库存业务都是独立部署的服务。用户下订单,会调用到订单服务和库存服务
比如:订单系统:订单服务 —> 库存服务 (PRC远程调用服务接口)
因为分布式部署,很有可能在调用库存服务时,因为网络等原因,订单服务调用失败,但其实库存服务已经处理完成,只是返回给订单服务处理结果时出现了异常。这个时候一般系统会作补偿方案,也就是订单服务再此放起库存服务的调用,库存减1:update t_goods set count = count -1 where good_id=2
这样就出现了问题,上一次调用已经减了1,只是订单服务没有收到处理结果。现在又调用一次,又减1,这样就不符合业务了。幂等是,不管库存服务在相同条件下调几次,处理结果都一样。这样保证补偿方案的可行性
问题场景:
- 用户重复操作
- 代码重试
- 消息重复消费
- 网络波动
幂等分析:单库单表
- select 幂等:每次查询对数据都不会产生副作用
- insert 不一定幂等:当我们重复插入数据的时候
- 自增主键,无幂等性:insert into product_info (name,type,price)
- 业务主键,具有幂等:insert into product_info (orderId,name,type,price) orderId主键唯一
- delete 不一定幂等:当我们重复删除数据的时候
- 绝对删除,具有幂等:delete from order where orderId = 3
- 相对删除,无幂等性:delete from order where orderId > 23
- update 不一定幂等:当我们重复更新数据的时候
- 绝对更新,具有幂等:update good set stock= 586 where goodId = 10
- 相对更新,无幂等性:update good set stock = stock+10 where goodid= 10
接口幂等性解决方案
-
唯一索引去重
防止新增脏数据
-
Token + Redis
防止页面重复提交:由于重复点击或者网络重发或nginx重发等情况会导致数据被重复提交
-
获取全局唯一token:接口处理生成唯一标识(token) 存储到redis中,并返回给调用客户端
后台校验token,同时删除token,生成新的token返回。redis要用删除操作来判断token,删除成功代表token校验通过,如果用select+delete来校验token,存在并发问题,不建议使用
-
发起支付操作并附带token
- 获得分布式锁(处理并发情况:在高并发请求中 ,token判断是否存在是非线程安全)
- 判断redis中是否存在token:存在-执行支付业务逻辑,否则-返回该订单已经支付
- 释放分布式锁(处理并发情况:在高并发请求中 ,token判断是否存在是非线程安全)
-
-
状态机
电商订单支付状态: 0-待支付、1-支付中、3-支付成功、4-支付失败
update order set status = 1 where status =0 and orderId = “201251487987”
进行订单支付,上来先用CAS更新订单状态在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机,就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等
-
乐观锁
数据库乐观锁:
update t_goods set count=count-1,version=version+1 where good_id=2 and version=1
-
分布式锁
如果是分布是系统,构建全局唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统(redis或zookeeper),在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁
-
全局唯一ID