带你详细了解Redis事务锁机制-加实列演示-加连接池-包括解决遗留问题-下

news2024/10/6 14:37:48

Redis_事务_锁机制_秒杀

连接池技术

连接池介绍

1、节省每次连接redis 服务带来的消耗,把连接好的实例反复利用。

2、链接池参数

  • MaxTotal:控制一个pool 可分配多少个jedis 实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制
  • -maxIdle:控制一个pool 最多有多少个状态为idle(空闲)的jedis 实例
  • -MaxWaitMillis:表示当获取一个jedis 实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException
  • -testOnBorrow:获得一个jedis 实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis 实例均是可用的

使用连接池, 优化连接超时

通过连接池,可以指定连接超时时间, 这个连接超时时间,也需要合理设置,要考虑到用户的实际体验

创建JedisPoolUtil

src\com\seckill\utils\JedisPoolUtil.java

public class JedisPoolUtil {
    
    //解读volatile作用
    //1. 线程的可见性: 当一个线程去修改一个共享变量时, 另外一个线程可以读取这个修改的值
    //2. 顺序的一致性: 禁止指令重排
    private static volatile JedisPool jedisPool = null;
    private JedisPoolUtil() {
    }
    //保证每次调用返回的 jedisPool是单例-这里使用了双重校验
    public static JedisPool getJedisPoolInstance() {

        if (null == jedisPool) {
            synchronized (JedisPoolUtil.class) {
                if (null == jedisPool) {
                    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
                    //对连接池进行配置
                    jedisPoolConfig.setMaxTotal(200);
                    jedisPoolConfig.setMaxIdle(32);
                    jedisPoolConfig.setMaxWaitMillis(60 * 1000);
                    jedisPoolConfig.setBlockWhenExhausted(true);
                    jedisPoolConfig.setTestOnBorrow(true);
                    jedisPool = new JedisPool(jedisPoolConfig, "192.168.198.135", 6379, 60000);
                }
            }
        }
        return jedisPool;
    }
    //释放连接资源
    public static void release(Jedis jedis) {

        if(null != jedis) {
            jedis.close();//如果这个jedis是从连接池获取的,这里jedis.close(),就是将jedis对象/连接,释放到连接池
        }
    }
}

修改SecKillRedis

ticket\src\com\seckill\redis\SecKillRedis.java

//- 连接到Redis, 得到jedis对象
        //Jedis jedis = new Jedis("192.168.198.135", 6379);

        //- 通过连接池获取到jedis对象/连接
        JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedisPoolInstance.getResource();
        System.out.println("---使用的连接池技术----");

测试可用即可

在这里插入图片描述

线程可见性

–简单说一下指令重排
在这里插入图片描述

版本3:利用Reids 事务机制,解决超卖

1、控制超卖-Redis 事务底层(乐观锁机制分析)

在这里插入图片描述

2 、修改SecKillRedis

src\com\seckill\redis\SecKillRedis.java

public class SecKillRedis {

    /**
     * 编写一个测试方法-看看是否能够连通到指定的Redis
     */

    @Test
    public void testRedis() {

        Jedis jedis = new Jedis("192.168.198.135", 6379);
        //jedis.auth("foobared");//如果需要认证, 就使用auth
        System.out.println(jedis.ping());
        jedis.close();
    }

    /**
     * 秒杀过程/方法
     */

    /**
     * @param uid      用户id - 在后台生成
     * @param ticketNo 票的编号, 比如北京-成都的ticketNo 就是bj_cd
     * @return
     */
    public static boolean doSecKill(String uid, String ticketNo) {

        //- uid 和 ticketNo进行非空校验
        if (uid == null || ticketNo == null) {
            return false;
        }
        //- 连接到Redis, 得到jedis对象
        //Jedis jedis = new Jedis("192.168.198.135", 6379);

        //- 通过连接池获取到jedis对象/连接
        JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedisPoolInstance.getResource();
        System.out.println("---使用的连接池技术----");

        //- 拼接票的库存key
        String stockKey = "sk:" + ticketNo + ":ticket";

        //- 拼接秒杀用户要存放到的set集合对应的key,这个set集合可以存放多个userId
        String userKey = "sk:" + ticketNo + ":user";

        //监控库存
        jedis.watch(stockKey);

        //- 获取到对应的票的库存, 判断是否为null
        String stock = jedis.get(stockKey);
        if (stock == null) {
            System.out.println("秒杀还没有开始, 请等待..");
            jedis.close(); //如果jedis是从连接池获取的,则这里的close就是将jedis对象/连接释放到连接池
            return false;
        }

        //- 判断用户是否重复秒杀/复购
        if (jedis.sismember(userKey, uid)) {
            System.out.println(uid + " 不能重复秒杀...");
            jedis.close();
            return false;
        }

        //- 判断火车票,是否还有剩余
        if (Integer.parseInt(stock) <= 0) {
            System.out.println("票已经卖完了, 秒杀结束..");
            jedis.close();
            return false;
        }

        - 可以购买
        1. 将票的库存量-1
        //jedis.decr(stockKey);
        2. 将该用户加入到抢购成功对应的set集合中
        //jedis.sadd(userKey, uid);

        //使用事务,完成秒杀
        Transaction multi = jedis.multi();

        //组队操作
        multi.decr(stockKey);//减去票的库存
        multi.sadd(userKey, uid);//将该用户加入到抢购成功对应的set集合中

        //执行
        List<Object> results = multi.exec();

        if(results == null || results.size() == 0) {
            System.out.println("抢票失败...");
            jedis.close();
            return false;
        }

        System.out.println(uid + " 秒杀成功..");
        jedis.close();
        return true;

    }
}

3、完成测试

  1. 重启Tomcat
  2. 重置Redis 相关数据

在这里插入图片描述

  1. 执行指令
ab -n 1000 -c 100 -p ~/postfile -T application/x-www-form-urlencoded http://192.168.98.1:8080/seckill/secKillServlet

在这里插入图片描述

在这里插入图片描述

版本4:抢票并发模拟,出现库存遗留问题

1、先重置一下redis 的数据
在这里插入图片描述

解读:

  1. 这里我们把库存量设的较大, 为600
  1. 执行指令
ab -n 1000 -c 300 -p ~/postfile -T application/x-www-form-urlencoded http://192.168.98.1:8080/seckill/secKillServlet

解读:

  1. 这里我们并发数变大-c 300
  2. 执行结果

在这里插入图片描述

  1. 可以看到, 剩余票数为543, 并不是0

  2. 出现库存遗留问题的分析

在这里插入图片描述

LUA 脚本

LUA 介绍

1、Lua 是一个小巧的脚本语言,Lua 脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua 并没有提供强大的库,一个完整的Lua 解释器不过200k,所以Lua 不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。

2、很多应用程序、游戏使用LUA 作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。

3、将复杂的或者多步的Redis 操作,写为一个脚本,一次提交给redis 执行,减少反复连接redis 的次数。提升性能。

4、LUA 脚本是类似Redis 事务,有一定的原子性,不会被其他命令插队,可以完成一些redis 事务性的操作

5、Redis 的lua 脚本功能,只有在Redis 2.6 以上的版本才可以使用

6、通过lua 脚本解决争抢问题,实际上是Redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题

LUA 脚本, 解决库存遗留-思路分析图

在这里插入图片描述

对上图解读

  1. LUA 脚本是类似Redis 事务,有一定的原子性,不会被其他命令插队,能完成Redis事务性的操作
  2. 通过lua 脚本解决争抢问题,Redis 利用其单线程的特性,将请求形成任务队列, 从而解决多任务并发问题

LUA 脚本, 解决库存遗留-代码实现

1、编写lua 脚本文件

local userid=KEYS[1]; -- 获取传入的第一个参数
local ticketno=KEYS[2]; -- 获取传入的第二个参数
local stockKey='sk:'..ticketno..:ticket; -- 拼接stockKey
local usersKey='sk:'..ticketno..:user; -- 拼接usersKey
local userExists=redis.call(sismember,usersKey,userid); -- 查看在redis 的
usersKey set 中是否有该用户
if tonumber(userExists)==1 then
	return 2; -- 如果该用户已经购买, 返回2
end
	local num= redis.call("get" ,stockKey); -- 获取剩余票数
	if tonumber(num)<=0 then
	return 0; -- 如果已经没有票, 返回0
else
	redis.call("decr",stockKey); -- 将剩余票数-1
	redis.call("sadd",usersKey,userid); -- 将抢到票的用户加入set
end
	return 1 -- 返回1 表示抢票成功

– 参考文档: https://blog.csdn.net/qq_41286942/article/details/124161359

2 、创建SecKillRedisByLua

\src\com\seckill\redis\SecKillRedisByLua.java

public class SecKillRedisByLua {

    /**
     * 说明
     * 1. 这个脚本字符串是在lua脚本上修改的, 但是要注意不完全是字符串处理
     * 2. 比如 : 这里我就使用了 \" , 还有换行使用了 \r\n
     * 3. 这些都是细节,如果你直接把lua脚本粘贴过来,不好使,一定要注意细节
     * 4. 如果写的不成功,就在这个代码上修改即可
     */
    static String secKillScript = "local userid=KEYS[1];\r\n" +
            "local ticketno=KEYS[2];\r\n" +
            "local stockKey='sk:'..ticketno..\":ticket\";\r\n" +
            "local usersKey='sk:'..ticketno..\":user\";\r\n" +
            "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
            "if tonumber(userExists)==1 then \r\n" +
            "   return 2;\r\n" +
            "end\r\n" +
            "local num= redis.call(\"get\" ,stockKey);\r\n" +
            "if tonumber(num)<=0 then \r\n" +
            "   return 0;\r\n" +
            "else \r\n" +
            "   redis.call(\"decr\",stockKey);\r\n" +
            "   redis.call(\"sadd\",usersKey,userid);\r\n" +
            "end\r\n" +
            "return 1";

    //使用lua脚本完成秒杀的核心方法
    public static boolean doSecKill(String uid,String ticketNo) {
        //先从redis连接池,获取连接
        JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedisPoolInstance.getResource();
        //就是将lua脚本进行加载
        String sha1 = jedis.scriptLoad(secKillScript);
        //evalsha是根据指定的 sha1校验码, 执行缓存在服务器的脚本
        Object result = jedis.evalsha(sha1, 2, uid, ticketNo);
        String resString = String.valueOf(result);

        //根据lua脚本执行返回的结果,做相应的处理
        if("0".equals(resString)) {
            System.out.println("票已经卖光了..");
            jedis.close();
            return false;
        }

        if("2".equals(resString)) {
            System.out.println("不能重复购买..");
            jedis.close();
            return false;
        }

        if("1".equals(resString)) {
            System.out.println("抢购成功");
            jedis.close();
            return true;
        } else {
            System.out.println("购票失败..");
            jedis.close();
            return false;
        }
    }
}

3 、修改SecKillServlet

src\com\seckill\web\SecKillServlet.java

public class SecKillServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 请求时,模拟生成一个userId
        String userId = new Random().nextInt(10000) + "";
        //2. 获取用户要购买的票的编号
        String ticketNo = request.getParameter("ticketNo");

        //3. 调用秒杀的方法
        //boolean isOk = SecKillRedis.doSecKill(userId, ticketNo);

        //4. 调用lua脚本完成秒杀方法
        boolean isOk = SecKillRedisByLua.doSecKill(userId, ticketNo);

        //4. 将结果返回给前端-这个地方可以根据业务需要调整
        response.getWriter().print(isOk);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/663432.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

.NetCore gRpc 客户端与服务端的单工通信Demo

文章目录 .NetCore gRpc 客户端与服务端的单工通信Demo服务端方式一方式二 客户端proto协议文件syntax "proto3";import "google/protobuf/empty.proto";serviceproto3与.netCore 的类型对应日期和时间可为 null 的类型字节小数为 Protobuf 创建自定义 de…

<Linux开发>驱动开发 -Linux MISC 驱动

&#xff1c;Linux开发&#xff1e;驱动开发 -Linux MISC 驱动 交叉编译环境搭建&#xff1a; &#xff1c;Linux开发&#xff1e; linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下&#xff1a; &#xff1c;Linux开发&#xff1e; -之-系统移植 uboot移植过程详细记…

软考高级系统架构设计师(二) 基础知识之计算机组成与系统结构2

目录 总线 ​CISC与RISC 流水线技术 总线 练习题&#xff1a; CISC与RISC RISC(精简指令集计算机)和CISC(复杂指令集计算机)是当前CPU的两种架构. RISC与CICS的比较 1.RISC比CICS更能提高计算机运算速度&#xff1b;RISC寄存器多&#xff0c;就可以减少访存次数&#xff0c;…

生产环境Java应用服务内存泄漏分析与解决

有个生产环境CRM业务应用服务&#xff0c;情况有些奇怪&#xff0c;监控数据显示内存异常。内存使用率99.%多。通过生产监控看板发现&#xff0c;CRM内存超配或内存泄漏的现象&#xff0c;下面分析一下这个问题过程记录。 1、服务器硬件配置部署情况 生产服务器采用阿里云ECS…

【命令参数】MSBuild - 环境配置及常用命令参数

目录 环境配置 基本语法 参数指令 对各类程序的命令参数的掌握是软件工程师必修课之一&#xff0c;它是通往自动化、高效化开发测试的必经之路。对于MSBuild&#xff0c;我们可以借助它以一种轻量级的形式去完成对于项目又或解决方案的生成&#xff0c;而避开使用繁大的IDE进…

Linux系统之部署Etherpad文档编辑器

Linux系统之部署Etherpad文档编辑器 一、Etherpad介绍1.Etherpad简介2.Etherpad特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本3.3 检查系统是否安装Node.js 四、部署Node.js 环境4.1 下载Node.js安装包…

【吴恩达老师《机器学习》】课后习题3之【逻辑回归解决多分类】与【神经网络】笔记(代码注释详细)

本次习题所用到的数据&#xff0c;#数据集&#xff1a;ex3data1.mat&#xff0c;参数&#xff1a;ex3weights.mat。在文章开头&#xff0c;下载即可&#xff01; 逻辑回归解决多分类问题 二分类VS多分类 在机器学习中&#xff0c;分类是一种监督学习任务&#xff0c;其中我们…

从机缘到成就

机缘 在这1825天的创作之旅中&#xff0c;我收获了许多宝贵的机遇和经验。起初&#xff0c;我只是一个对技术有着浓厚兴趣的普通人&#xff0c;遇到了一个在eclipse导入工程后出现中文乱码的问题。而我决定将这个问题记录下来&#xff0c;并分享给其他可能遇到相同困扰的人们。…

数据库系统概述——第三章 关系数据库标准语言SQL(知识点复习+练习题)

&#x1f31f;博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;离散数学考前复习&#xff08;知识点题&#xff09; &#x1f353;专栏&#xff1a;概率论期末速成&#xff08;一套卷&#xff09; &#x1f433;专栏&#xff1a;数字电路考前复习 &#x1f99a;专栏&am…

Linux基础内容(23)—— 信号补充与多线程交接知识

Linux基础内容&#xff08;22&#xff09;—— 信号_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/130835485 目录 1.可重入函数 1.情况假设 2.volatile 3.SIGCHLD信号 1.SIGCHLD介绍 2.信号的确认 3.wait的处理 1.可重入函数 1.情况假设…

插件 - 通过SPI方式实现插件管理

文章目录 SPI概念基本原理使用步骤优点缺点Code真实使用场景案例JDBC(Java Database Connectivity)Servlet API日志框架SPI概念 SPI(Service Provider Interface)是Java提供的一种服务扩展机制,它允许应用程序在运行时动态加载和发现提供者(Providers),并与它们进行交…

Proteus仿真之UART通信(点亮LED灯)

1.UART通信简介&#xff1a;通用异步收发传输器UART(Universal Asynchronous Receiver/Transmitter)是负责处理数据总线和串口之间的串/并通信的设备。UART通信规定了数据帧的格式&#xff1a;起始位、数据位、校验位、停止位等。UART异步通信只需要通信双方设置好数据帧的格式…

房屋装修选择自装,如何寻找水电工人,比价并施工(水电阶段)

环境&#xff1a; 地点&#xff1a;杭州 装修类型&#xff1a;自装 面积&#xff1a;建面135平方 进度&#xff1a;水电阶段 问题描述&#xff1a; 房屋装修选择自装&#xff0c;如何寻找水电工人&#xff0c;比价并施工 解决方案&#xff1a; 一、了解水电相关知识 水…

Python3+RIDE+RobotFramework自动化测试框架搭建

Python2.7已于2020年1月1日开始停用&#xff0c;之前RF做自动化都是基于Python2的版本。 没办法&#xff0c;跟随时代的脚步&#xff0c;我们也不得不升级以应用新的控件与功能。 升级麻烦&#xff0c;直接全新安装。 一、Python安装 最新版Python下载地址&#xff1a;http…

Qt连接Access数据库

Qt自带有QODBC驱动&#xff08;封装了ODBC驱动接口&#xff09;&#xff0c;通过windows平台上提供的ODBC驱动访问支持ODBC的数据库&#xff0c;如Ms Access、SQL Server等 (Windows XP 自带有Access和SQL Server的ODBC Driver)。我们就用QODBC对Access数据库进行访问。 Acces…

别再瞎搞了,耳朵都竖起来听我说,新手小白开发应该如何选择最合适你的JetBrains IDE版本类型和版本号! 今天一次性给你说清楚!

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&#x1…

windows环境下搭建redis集群

下面记录一下windows10环境下搭建redis3主3从集群&#xff0c;将过程分享出来&#xff0c;仅供学习研究使用。 1、redis集群 Redis集群关键点就是去掉中心化(与哨兵模式的区别)&#xff0c;当主机宕机&#xff0c;从节点回自动升级为主节点&#xff0c;具体请参考官网或相关大…

机器学习——KNN算法(手动代码,含泪)

徒手实现代码的过程&#xff0c;真是含泪和心酸&#xff0c;浪费了生命中的三天&#xff0c;以及工作中的划水一小时 终于滤清思路后&#xff0c;自己实现了KNN 都说KNN是最基础&#xff0c;最简单的分类器 放屁&#xff01;骗纸&#xff01;&#xff01;&#xff01;它的想法是…

第八章——向量代数与空间解析几何

目录 一、运算公式 二、平面的法线向量 注&#xff1a;加粗体为向量 一、运算公式 1.若a//b&#xff0c;那么aλb 若a⊥b&#xff0c;那么a*b0 2.若A(x1,y1,z1)&#xff0c;B(x2,y2,z2) 中点坐标&#xff1a;AB中点M(x1x2/2,y1y2/2,z1z2/2) 两点间的距离和模的计算&#x…

第3章 信息系统治理

文章目录 3.1.1 IT治理基础1. IT治理的驱动因素2. IT治理的目标价值3. IT治理的管理层次 3.1.2 IT治理体系1. IT治理关键决策2. IT治理体系框架3. IT治理核心内容4. IT治理机制经验&#xff08;建立IT治理机制的原则&#xff1a;简单、透明、适合&#xff09; 3.1.3 IT治理任务…