spring boot:用redis+lua实现表单接口的幂等性(spring boot 2.2.0)

一,什么是幂等性?

1,幂等:

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

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

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

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

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

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

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

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

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

    unique索引

    悲观锁

    乐观锁

   token机制

    ...

  

说明:刘宏缔的架构森林是一个专注架构的博客,

网站:https://blog.imgtouch.com
本文: https://blog.imgtouch.com/index.php/2023/05/22/springboot-yong-redislua-shi-xian-biao-dan-jie-kou-de-mi-deng-xing-springboot220/

         对应的源码可以访问这里获取: 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)

 

posted @ 2020-06-03 10:44  刘宏缔的架构森林  阅读(1163)  评论(0编辑  收藏  举报