SpringBoot使用LUA解决Redis库存遗留问题

news/2024/7/9 23:52:52 标签: nginx, 负载均衡, 服务器

SpringBoot使用LUA解决Redis库存遗留问题

前面,我的博客提到了怎么用Redis的乐观锁解决超卖问题。但是,使用乐观锁其实,有一个缺点,就是我们假设现在有2000次请求,并发数为200,此时的库存如果比较大的话,比如是500,那么,我们最后会发现,这2000次请求最后会有很多次因为乐观锁机制的影响导致的抢购失败。

这个问题要解决,我们可以使用我们的LUA。
简单介绍一下,LUA是一个小巧的脚本语言,他不适合作为开发独立应用程序的语言,但是却可以作为我们嵌入式的脚本语言。很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。

那么,使用过LUA在Redis中有什么优势呢?将复杂或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数,提升性能。

LUA脚本类似于redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。可以很好地解决我们redis的超卖问题和库存遗留问题

注意:Redis版本必须要在2.6以上才可以使用LUA脚本。通过LUA脚本解决争抢问题,实际上是redis利用其单线程的特性,用任务队列的方式解决多任务并发问题。

废话不多说,我们直接给源码:

@RestController
public class RedisController {
    static String secKillScript =
            "local userid=KEYS[1];\r\n"+
            "local prodid=KEYS[2];\r\n"+
            "local kcKey=\"sk:\"..prodid..\":qt\";\r\n"+
            "local userKey=\"sk:\"..prodid..\":user\"\r\n"+
            "local userExists=redis.call(\"sismember\",userKey,userid);\r\n"+
            "if tonumber(userExists)==1 then \r\n"+
            "    return 2;\r\n"+
            "end\r\n"+
            "local num=redis.call(\"get\", kcKey);\r\n"+
            "if tonumber(num)<=0 then \r\n"+
            "    return 0;"+
            "else "+
            "    redis.call(\"decr\",kcKey);\r\n"+
            "    redis.call(\"sadd\", userKey, userid);\r\n"+
            "end\r\n"+
            "return 1";
            
    @PostMapping("/secKill2")
    public void TestLua(){
        //用户id,我们使用随机数来表示每次访问的不同用户
        String userid = new Random().nextInt(50000) + "";
        //商品id,我们先写死
        String prodid = "1001";

        //1、连接redis(有密码记得加jedis.auth("密码"))
        Jedis jedis = new Jedis("119.91.153.74", 6379);

        String sha1 = jedis.scriptLoad(secKillScript);
        Object result = jedis.evalsha(sha1, 2, userid, prodid);

        String reString = String.valueOf(result);
        if("0".equals(reString)){
            System.out.println("已经被抢空");
        }else if ("1".equals(reString)){
            System.out.println("抢购成功!");
        }else if ("2".equals(reString)){
            System.out.println("您已经抢购过了,请勿重复抢购!");
        }else{
            System.out.println("抢购异常");
        }
        jedis.close();
    }
}

解释一下源码:

    static String secKillScript =
            "local userid=KEYS[1];\r\n"+
            "local prodid=KEYS[2];\r\n"+
            "local kcKey=\"sk:\"..prodid..\":qt\";\r\n"+
            "local userKey=\"sk:\"..prodid..\":user\"\r\n"+
            "local userExists=redis.call(\"sismember\",userKey,userid);\r\n"+
            "if tonumber(userExists)==1 then \r\n"+
            "    return 2;\r\n"+
            "end\r\n"+
            "local num=redis.call(\"get\", kcKey);\r\n"+
            "if tonumber(num)<=0 then \r\n"+
            "    return 0;"+
            "else "+
            "    redis.call(\"decr\",kcKey);\r\n"+
            "    redis.call(\"sadd\", userKey, userid);\r\n"+
            "end\r\n"+
            "return 1";
            "local userid=KEYS[1];\r\n"+
            "local prodid=KEYS[2];\r\n"+

local表示变量。相当于我们在js中使用let。
KEYS表示参数,你可以简单地理解成这段脚本是一个函数,KEYS是传过来的参数的数组。其中KEYS[1]表示第一个参数,KEYS[2]表示第二个参数。

"local kcKey=\"sk:\"..prodid..\":qt\";\r\n"

这个也很简单,就是我们java里面字符串的拼接,只不过,这里将+改为了…(两个点)。

userExists=redis.call(\"sismember\",userKey,userid);\r\n"

这一段就是让redis调用命令sismember,然后将他的参数 userKey 和 userid 传进去。

tonumber(userExists)==1

这一段,就是把字符串转化为Number类型。

至于return 0,return 1,return 2。这个其实是我们自己定义的。这里我们规定,返回0表示库存为0,返回1表示用户抢购成功,返回2表示用户已经抢购成功,且打算抢购第二次,对其进行阻止。

        String sha1 = jedis.scriptLoad(secKillScript);
        Object result = jedis.evalsha(sha1, 2, userid, prodid);

这里String sha1 = jedis.scriptLoad(secKillScript);表示的是加载LUA脚本,jedis.evalsha(sha1, 2, userid, prodid);这个方法表示执行脚本,四个参数分别表示的是:加载好的脚本、该脚本接收的参数个数,第一个参数,第二个参数)。

ok,代码写完了,现在开始演示:
先清空数据库,然后设置库存为100.
在这里插入图片描述
然后,我们用python跑一下我们的ab工具,模拟并发操作:

import os
#D:\Download\httpd-2.4.51-o111l-x64-vc15\Apache24\bin\ab是我们的ab工具路径
ab=os.popen(r'D:\Download\httpd-2.4.51-o111l-x64-vc15\Apache24\bin\ab -n 2000 -c 200 -p ./postfile -T application/x-www-form-urlencoded http://192.168.148.148:8080/secKill')
print(ab.read())

运行结果:
在这里插入图片描述
我们再看看Redis此时的数据:
库存刚好为0。
在这里插入图片描述
添加的用户刚好为100。演示成功!
在这里插入图片描述
在这里插入图片描述


http://www.niftyadmin.cn/n/1733351.html

相关文章

第六章--FusionCharts Free图形的基本元素

[AJava原创]FusionCharts Free中文开发指南[使用文档教程]第六章--FusionCharts Free图形的基本元素时间&#xff1a;2009-01-12 15:39 来源&#xff1a;AJava.org 作者&#xff1a;道长A核心提示&#xff1a;我们需要搞清楚图形的组成部分&#xff0c;比如什么是X轴&…

第七章--FusionCharts Free和XML

[AJava原创]FusionCharts Free中文开发指南[使用文档教程]第七章--FusionCharts Free和XML时间&#xff1a;2009-01-12 18:38 来源&#xff1a;AJava.org 作者&#xff1a;道长A喜欢本页内容吗&#xff1f;那就收藏到您的博客吧。如果您有以下书签网站的账号&#xff0c;…

第八章--FusionCharts Free和组合图XML

[AJava原创]FusionCharts Free中文开发指南[使用文档教程]第八章--FusionCharts Free和组合图XML时间&#xff1a;2009-01-12 22:23 来源&#xff1a;AJava.org 作者&#xff1a;道长A喜欢本页内容吗&#xff1f;那就收藏到您的博客吧。如果您有以下书签网站的账号&#…

redis配置主从服务器后,总是显示master_link_status:down的解决方法

redis配置主从服务器后&#xff0c;总是显示master_link_status:down的解决方法 解决问题一、你的主机设置了密码&#xff0c;那么我们需要在从机里面写上你主机的密码 所以我们的redis6380.conf 和 redis6381.conf 文件的内容应该如下&#xff1a;&#xff08;这里我顺便把主…

第十章--FCF中的下钻

[AJava原创]FusionCharts Free中文开发指南[使用文档教程]第十章--FCF中的下钻时间&#xff1a;2009-01-15 17:14 来源&#xff1a;AJava.org 作者&#xff1a;道长A喜欢本页内容吗&#xff1f;那就收藏到您的博客吧。如果您有以下书签网站的账号&#xff0c;点击它即可收…

第十一章--FCF中的基本数字格式

[AJava原创]FusionCharts Free中文开发指南[使用文档教程]第十一章--FCF中的基本数字格式时间&#xff1a;2009-01-16 17:48 来源&#xff1a;AJava.org 作者&#xff1a;道长A喜欢本页内容吗&#xff1f;那就收藏到您的博客吧。如果您有以下书签网站的账号&#xff0c;点…

Redis主从复制的复制原理

Redis主从复制的复制原理 主从复制数据同步的底层是&#xff1a; Slave 成功连接Master后&#xff0c;会发送一个sync命令。Master借到命令后&#xff0c;启动后台的存盘进程&#xff0c;同时收集所有接收到的用于修改数据集命令&#xff0c;在后台进程执行完毕之后&#xff…

第十三章--间断数据的处理

[AJava原创]FusionCharts Free中文开发指南[使用文档教程]第十三章--间断数据的处理时间&#xff1a;2009-01-17 20:17 来源&#xff1a;AJava.org 作者&#xff1a;道长A喜欢本页内容吗&#xff1f;那就收藏到您的博客吧。如果您有以下书签网站的账号&#xff0c;点击它…