zoukankan      html  css  js  c++  java
  • spring boot:用redis+lua实现表单接口的幂等性(spring boot 2.2.0)

    一,什么是幂等性?

    1,幂等:

       幂等操作:不管执行多少次,所产生的影响都和一次执行的影响相同。

       幂等函数或幂等方法:可以使用相同的参数重复执行,并能获得相同的结果的函数/方法。

       这些函数/方法不用担心重复执行会对系统造成改变。

    2,幂等操作的一些例子:

       前端重复提交相同的数据,后台只产生对应这个数据的一个相同的反应结果

       发送验证码短信:应该只发一次,相同的验证短信不能多次发送。

       生成订单:一个业务请求只能创建一个订单,不能重复创建相同的订单

       用户付款:只能扣用户一次钱,不能重复扣费

    3,实现幂等操作的一些方法:

        unique索引

        悲观锁

        乐观锁

       token机制

        ...

      

    说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

             对应的源码可以访问这里获取: https://github.com/liuhongdi/

    说明:作者:刘宏缔 邮箱: 371125307@qq.com

    二,关于演示代码的说明:

    1,项目的原理:

    我们这里的演示的是表单提交时要避免重复提交相同的内容,

    前端在用户点击提交按钮后,需要在后端返回结果之前,禁止用户再次点击提交按钮,

    假如有请求绕过了前端的控制,直接向后端发送重复的相同请求,

    后端如何避免?

    在用户打开表单时,后端会生成一个token字符串,保存在redis后,传递给表单,

    当表单提交时,这个字符串会再次提交到后端接口,

    后端接口需要判断这个字符串是否在redis中存在?

    如果不存在不允许提交,如果存在删除时能成功删除,允许提交,

    删除时报错:表示已被其他进程删除,也不能允许提交.

    2,项目在github的地址:

    https://github.com/liuhongdi/idempotent

    3,代码结构截图:

    三,lua代码的说明:

    checkidem.lua

    local current = redis.call('GET', KEYS[1])
    if current == false then
        --redis.log(redis.LOG_NOTICE,KEYS[1]..' is nil ')
        return '-1'
    end
    local isdel = redis.call('DEL', KEYS[1])
    if isdel == 1 then
         --redis.log(redis.LOG_NOTICE,' del '..KEYS[1]..' success')
         return '1';
    else
         --redis.log(redis.LOG_NOTICE,'del '..KEYS[1]..' failed')
         return '0';
    end

    如果当前token在redis中不存在,返回 -1

    如果token存在,删除成功,返回1

    删除失败,返回0

    说明:为什么使用lua脚本?

    redis上的lua脚本的执行是原子性的,不存在多个线程的并发问题,

    使用lua脚本能保证不会出现重复的提交

    四,java代码的说明:

    1,RedisLuaUtil

    @Service
    public class RedisLuaUtil {
        @Resource
        private StringRedisTemplate stringRedisTemplate;
    
        /*
        run a lua script
        luaFileName: lua file name,no path
        keyList: list for redis key
        return 0: delete fail
               -1: no this key
               1: delete success
        */
        public String runLuaScript(String luaFileName,List<String> keyList) {
            //System.out.println("redis script begin");
            DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/"+luaFileName)));
            redisScript.setResultType(String.class);
    
            String argsone = "none";
            //System.out.println("execute begin");
            String result = stringRedisTemplate.execute(redisScript, keyList,argsone);
            System.out.println("lua result:"+result);
    
            return result;
        }
    }

    说明:

    DefaultRedisScript:负责封装lua脚本

    luaFileName: lua文件名

    keyList:   redis中的key列表,我们只需要传递token即可

    stringRedisTemplate:负责执行脚本

    argsone:值参数,我们传一个空字串即可

    2,TokenServiceImpl.java中对redisLuaUtil类的调用

        @Override
        public void checkToken(HttpServletRequest request) {
    
            String token = request.getHeader(TOKEN_NAME);
    
            if (StringUtils.isBlank(token)) {// header中不存在token
                token = request.getParameter(TOKEN_NAME);
                if (StringUtils.isBlank(token)) {// parameter中也不存在token
                    //System.out.println("-----no token");
                    throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getMsg());
                }
            }
    
            //System.out.println("runlua begin");
            List<String> keyList = new ArrayList();
            keyList.add(token);
            String res = redisLuaUtil.runLuaScript("checkidem.lua",keyList);
    
            if (res.equals("1")) {
                ServerResponseUtil.success("success");
            } else {
                throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg());
            }
        }

    五,测试幂等性的检测是否生效?

    提交表单前,先得到token

    访问:

    http://127.0.0.1:8080/order/gettoken

    返回:

    {"status":0,"msg":"8IgWPMtotKyzO13pnxCS9pc4","data":null}

    用ab测试表单:

    #-c:指定请求的并发数量

    #-n:指定请求的总数量

    [root@localhost etc]# ab -c 10 -n 10 http://127.0.0.1:8080/order/addorder?form_token=8IgWPMtotKyzO13pnxCS9pc4

    查看代码中system.out.println的打印输出:

    lua result:1
    lua result:-1
    lua result:-1
    lua result:-1
    lua result:-1
    lua result:-1
    lua result:-1
    lua result:-1
    lua result:-1
    lua result:-1

    可以看到只有一个是提交成功,其他的请求均给出了报错

    六,查看spring boot的版本:

      .   ____          _            __ _ _
     /\ / ___'_ __ _ _(_)_ __  __ _    
    ( ( )\___ | '_ | '_| | '_ / _` |    
     \/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.2.0.RELEASE)
  • 相关阅读:
    科技公司网站
    jquery 设置元素内容html(),text(),val()
    jquery 相关class属性的操作
    jquery attr()和prop()方法的使用
    检测移动设备横竖屏
    设定程序在某个特定时刻执行
    js设计模式-建造者模式
    css自定义字体完美解决方案example
    css透明属性
    css3多列example
  • 原文地址:https://www.cnblogs.com/architectforest/p/13033221.html
Copyright © 2011-2022 走看看