1、接口幂等性设计
现如今我们的系统大多拆分为分布式SOA,或者微服务,一套系统中包含了多个子系统服务,而一个子系统服务往往会去调用另一个服务,而服务调用服务无非就是使用RPC通信或者restful,既然是通信,那么就有可能在服务器处理完毕后返回结果的时候挂掉,这个时候用户端发现很久没有反应,那么就会多次点击按钮,这样请求有多次,那么处理数据的结果是否要统一呢?那是肯定的!尤其在支付场景。
2. 什么是接口幂等性
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...,这就没有保证接口的幂等性
2.什么情况需要处理接口幂等性问题?
2.1 select 天然自带幂等性。 每次查询对数据都不会产生副作用。
2.2 insert 当我们重复插入数据的时候,会出现什么情况 ? 第一种情况:自增主键,没有幂等性。
eg:insert into product_info (id,name,type,price,tm) 执行多次,会新增多条记录。对结果集产生了副作用。
第二种情况:业务主键,具有幂等。
eg:insert into product_info (orderId,name,type,price,tm) orderId 为主键唯一 无论该sql执行多少次,对结果集产生的效果都是一样只增加了一条数据。
2.3 delete 是否具有幂等性? 第一种情况:绝对删除,具有幂等性。
eg;delete from order where id = 3 。 无论该sql执行多少次,对结果集产生的效果都是一样只删除了一条数据。
第二种情况: 相对删除,不具有幂等性。
eg:delete from order where id > 23 . 该操作每执行一次,对结果集产生的结果,可能都不一样,同一操作多次执行对数据产生了副作用。
2.4 update 猜一猜是否具有天热幂等性? 第一种情况:绝对更新,具有幂等性。
eg:update good set stock= 586 where goodId = 10; 该操作无论执行多少次操作对结果的影响都是一样。
第二种情况:相对更新,不具有幂等性。
eg:update good set stock = stock+10 where goodid= 10 ; 每次执行该操作库存数量都会加10,所以不具备幂等操作。
总结:以上都是基于单库,单表的操作幂等性的分析,其实在具体业务当中,可能要设计多个表,多个库,甚至跨服务操作。比如分布式系统中,我们一个接口,可能需要调用多个服务来完成任务。那么这种情况,如何保证接口的幂等性呢?
接口幂等性解决方案
前言:接口幂等处理要根据具体业务来判断怎么处理,以下会举例来阐述接口幂等处理解决方案。
1.token+redis 机制 比如订单支付场景: 该支付分为两个步骤: 1.1 获取全局唯一token 接口处理生成唯一标识(token) 存储到redis中,并返回给调用客户端。 1.2 发起支付操作并附带token 接口处理: 1.2.1 获得分布式锁(处理并发情况) 1.2.2 判断redis中是否存在token 1.2.3 存在 执行支付业务逻辑,否则返回该订单已经支付 1.2.4 释放分布式锁
思考:为什么要加分布式锁? 原因1:在高并发请求中 ,token判断是否存在是非线程安全的,所以要加分布式锁来保证 该条件的判断为线程安全 注释:也可redis用删除操作来判断token,删除成功代表token校验通过 这个删除是原子操作的
原因2:在支付业务中,判断支付订单是否已经存在,存在说明该订单已经支付过了,不存在就执行扣款操作,如果相同操作并发两个请求来到判断条件可能两个请求都能判断支付订单不存在,造成重复扣款。 所以也要加分布式锁保证线程的安全。
2.CAS 保证接口幂等性
2.1 状态机制幂等(状态不可逆) 针对更新操作: 例如 电商订单,订单支付状态 0 待支付 , 1 支付中 , 3 支付成功 4 支付失败。
update order set status = 1 where status =0 and orderId = “201251487987” 该sql语句利用状态CAS 保证该操作的幂等。
eg:比如要进行订单支付,上来先用CAS更新订单状态,
返回影响说为1 代表修改成功,可以支付,继续执行支付业务代码
返回影响数 0 代表修改失败,该订单已经不是待支付订单了。
注释:实际这里是利用CAS原理
3 乐观锁实现幂等 背景由来:
为什么要有幂等这种场景?因为在大的系统中,都是分布式部署,如:订单业务 和 库存业务有可能都是独立部署的,都是单独的服务。用户下订单,会调用到订单服务和库存服务。
比如:订单系统: 订单服务 —> 库存服务 (PRC远程调用(服务接口))
因为分布式部署,很有可能在调用库存服务时,因为网络等原因,订单服务调用失败,但其实库存服务已经处理完成,只是返回给订单服务处理结果时出现了异常。这个时候一般系统会作补偿方案,也就是订单服务再此放起库存服务的调用,库存减1
update t_goods set count = count -1 where good_id=2
这样就出现了问题,其实上一次调用已经减了1,只是订单服务没有收到处理结果。现在又调用一次,又要减1,这样就不符合业务了,多扣了。
幂等这个概念就是,不管库存服务在相同条件下调用几次,处理结果都一样。这样才能保证补偿方案的可行性。
乐观锁方案 借鉴数据库的乐观锁机制,如: update t_goods set count = count -1 , version = version + 1 where good_id=2 and version = 1
4 防重表
1.利用数据库建一张防重表(加唯一索引)
比如订单支付,
反正重复支付:订单号插入防重表 成功 执行支付业务逻辑,失败说明已经支付过。
防重表支付成功是否要删除: 1.可定期清除数据 2.也可结合 订单状态 ,在支付前查询订单状态为待支付 执行支付操作 ,操作后删除订单号 若 第二个请求插入防重表成功,但是这个时候查询订单状态失败。 (实际这个防重表就是实现了分布式锁)
- 缓存队列
将请求放入队列,后续使用异步任务处理队列中的数据,过滤掉重复的消息。 和防止重复消费道理是一样。