• 如何实现接口幂等性


    关于如何实现接口幂等性

    之前一直关于如何实现接口幂等性的问题,都是一知半解,最近终于弄清楚了一些,写这篇文章总结一下,至于什么是接口幂等性,不知道的小伙伴,可以自行百度。


    一、首先明白一个问题

    什么时候会出现接口幂等性?在增删查改中,只有insert 和update会出现接口幂等性的问题,select和delete是不会的,并且比如如下的update不论执行多少次,都是幂等的:

    UPDATE tab1 SET col1=1 WHERE col2=2
    
    • 1

    只有诸如此类的update才不是幂等的

    UPDATE tab1 SET col1=col1+1 WHERE col2=2
    • 1

    我们只要保证update和insert语句的接口是幂等的就可以了。


    二、在什么业务场景中容易出现幂等性问题,以及比较主流的解决方案

    现在我总结的有比较常用的有三类情况: 前端多次点击按钮几乎同时发送多个请求给后端分布式远程调用失败重试、消息队列重复消费

    一)前端快速多次点击

    1、方案:使用token令牌机制。

    1. 在redis中生成一个key,作为token返回给前端界面,这个key可以使用UUID生成,value值无所谓。就拿下订单来讲,当用户进入到下单,显示购物车中的商品之前,这时候前端应该已经向后端申请了一个token返回给了客户端。
    2. 用户点击下单的时候,会将token放在请求头中,传给后端,后端接收到请求以后,先去redis中查找有没有改token对应的值,如果没有则直接返回,下单成功。如果有的话,则证明是第一次处理请求,将redis中的key删除,然后正常处理业务。

    2、先删key还是先处理业务?
    先说答案:先删key,在处理业务
    3、为什么先处理业务,后删令牌不行? 因为前端点击的速断很快,所以非常容易当第二个请求来的时候,去redis中查找key到时候,key还是存在的,幂等性保证失败。
    4、采用什么方式删除令牌的时候才是正确的?
    设想一下,如果连续两次点击的速度非常快如下代码会不会有问题

    //如果两个请求非常快,第二个请求去redis中获取token的时候,
    //第一个请求还没有删,那么这两个请求就都会执行成功
    if(redis.get(key)==token){
       del(key);
       doService();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    所以,要将取reids中获取token进行比较和删除token设计成原子的,使用lua表达式

    
      String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
      Long res = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(key), orderToken);
    
    
    • 1
    • 2
    • 3
    • 4
    二)诸如fegin这种远程调用的失败重试问题

    这个问题是比如fegin这种远程接口调用失败,自动重试引起的,特点是这两次的远程调用请求完全相同。
    直接说解决方案: 可以生命一个唯一请求id,放在请求的Header中,被调用方接收到请求以后,首先将请求的唯一id存在reids,可以利用setNx指令进行防重,也可以利用redis的set。
    看到网上有说将fegin的自动重试功能禁止的,还有说创建一个拦截器,只要是完全一样的请求就拦截的,个人觉得不太靠谱

    三)消息重试

    直接上结论吧:

    1. 将业务设计成幂等的,比如在接收到消息之前,先去数据库查询下数据状态,状态对的话就不处理了
    2. 根据消息中的消息id,去redis中查找是否已经存在。

    三、其他解决幂等性问题的补充

    以上三种情况是我们开发过程中比较容易遇到的问题和常见的解决方案,掌握上面基本就可以解决大部分的幂等性问题,接下来介绍的情况只有特殊情况下可以解决,但是也好用。

    1)数据解决方案

    这种方案可以利用数据库完成幂等性的操作

    1. 利用数据库的唯一性约束
    比如我们新增一个订单,在已经生成订单号的时候,再insert,这个时候是可以保证唯一性的。
    2. 利用乐观锁

    //首先这个sql是原子的,其次能保证幂等
    //但是我想了下使用场景,只有version作为参数穿个某个方法,该方法再调用下面这个sql才可以,要不然
    //根据我们常见的业务,更新中之前去数据select,你查到的就是别人更新过的,不能保证幂等。
    update t_goods set count = count -1 , version = version + 1 where good_id=2 and version = 1
    
    • 1
    • 2
    • 3
    • 4

    四、分布式场景

    这种场景也是很常见的,必须要做幂等性,比如多台机器,每天机器上都有定时任务,当到达时间多台机器同时开始执行时候,如果不做幂等处理,那很容易重复处理,这个时候是使用redisson的提供的分布式锁就行,同时只允许一台机器执行,并且业务也要做成幂等的,执行之前先查询下数据的状态是否合格。

  • 相关阅读:
    【HTML——奇幻彩色粒子】(效果+代码)
    面向对象编程三⼤特性 --封装、继承、多态
    C---流
    java毕业设计GuiTar网站设计Mybatis+系统+数据库+调试部署
    【FreeRTOS】队列的使用
    基于php经贸时间轴小程序毕业设计-附源码211617
    前端如何在没有后端配合的情况下获取服务器时间?
    使用C语言+USRP B210从零开始实现无线通信(4) 接收检测与解调
    初学者程序员要学好.Net,只要学习这几个框架就够了
    Jmeter UI详细介绍及脚本生成,get,转发收藏
  • 原文地址:https://blog.csdn.net/Insect_boy/article/details/134294390