接口幂等性解决方案
后端幂等性实现:
-
唯一索引(简单粗暴的办法)
例如,如果我的id是自增主键,一定会有幂等性问题,因为会产生多条数据相同,但主键不同的数据。那如果是业务逐渐呢?我们把多个字段简历唯一索引,比如name,age,type三个字段建立唯一索引。这样就ok了!即使我的id相同,数据库也不会报错。
此方案可以限制重复插入数据,当插入数据重复时数据库就会抛异常,保证不会出现脏数据。比如我们在用户注册场景,存贮用户场景等,要求一些唯一记录的场景下可以使用。 -
token验证。比如客户端进入一个支付页面后,后端生成一个token返回给前端,当每次点击支付的时候都带上这个token,请求后端支付接口时进行验证。提交后后台校验token,执行提交逻辑,提交成功同时删除token,生成新的token更新redis ,这样当第一次提交后token更新了,页面再次提交携带的token是已删除的token后台验证会失败不让提交如果不是同一个token就不予处理。
-
乐观锁(既保证效率又保证幂等)
#每次更新version版本号+1,如果版本号不一致更新不成功!
<update id="update" parameterType="Order">
update t_order set status=#{status},version = version+1 where orderId=#{orderId} and version = #{version}
</update>
public boolean pay(String orderId,UserAccount userAccount){
//查询订单
Order order = orderMapper.selectById(orderId);
if(order.getStatus == 0){//是未支付状态
order.setStatus(1);//设置支付成功状态
int row = orderMapper.update(order);//更新order
if(row > 0){//更新成功,执行支付!
userAccountMapper.pay(order.getId,userAccount.getUserId);
return true;
}else{
return false;//支付失败
}
return true;
}else{
return true;//已经支付
}
}
-
增加防重表(这种方式和乐观锁原理差不多)
比如我们可以将唯一主键(orderId)作为防重表的唯一索引,如果请求支付时就向防重表里查一条记录,插入成功才能去支付,插入失败就不让执行支付操作。 -
分布式锁
在进入方法时,先去获取锁,假如获取到锁,就继续后面的流程。假如没有获取到锁,就等待锁的释放直到获取到锁,当执行完方法时,释放锁,当然,锁要设个超时时间,防止意外没有释放到锁,它可以用来解决分布式系统的幂等性;
常用的分布式锁实现方案是redis和zookeeper等工具。
使用分布式锁类似于防重表,将防重并发放到了缓存中,较为高效。思路相同,同一时间只能完成一次支付请求。 -
缓冲队列
将请求都快速接收下来,放入缓冲队列,后续使用异步任务处理队列中的请求,过滤掉重复的请求。
前端可执行的一些方案:
- 按钮加loading,一段时间内只让点击一次。
- 点击完就跳转页面,不让多停留。