专题五 Redis高并发场景

2023-11-11

介绍

Redis高并发场景,如果直接去学会比较抓不住头绪,因此本文将一步步介绍Redis的高并发的步骤演进。

首先解释synchronized不适合在分布式场景,因为synchronized只适用自身的JVM,因此在分布式场景下多台机器的情况下,可能会出现同时操作一个key,从而会出现两个服务同时进行商品购买后,商品数量只减1的情况。

分布式测试环境

为了模拟分布式场景,模拟电商库存售卖的场景,每次调用接口相当于输出货物然后库存减1。

下面搭建一个简易的分布式系统。配置一个Nginx进行负载均衡、启动两个服务去连接Redis

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ogP7th7I-1655106882784)(F:\技术积累\Redis学习.assets\image-20220613110032523.png)]

Nginx主要配置如下

upstream redislock{
		server 10.175.87.148:8080 weight=1;
		server 10.175.87.148:8090 weight=1;
	}
	server{
		listen       8000;
        server_name  localhost;
		
		location / {
			root html;
			index index.html index.htm;
			proxy_pass http://redislock;
		}
	}

接下来再开两个服务,服务主要提供对面提供操作Redis的接口。服务分别开启8090、8080端口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SHBSJU4Y-1655106882791)(F:\技术积累\Redis学习.assets\image-20220613145548382.png)]

核心接口程序

    @RequestMapping("/deduct_stock_syn")
    public String deductStockSyn(){
        //version 1
        synchronized (this){ //synchronized 只在一个JVM进程中生效,分布式集群环境下不可以执行
            //逻辑块

            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if(stock > 0){
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功,剩余库存:" + realStock + "");
            }else{
                System.out.println("扣减失败,库存不足");
            }
        }
        return "end";
    }

为了测试出synchronized不适用再分布式场景,我们采用Jmeter进行压测

1、在Path上添加上请求的路径 2、配置压测参数,当前设置的是并发200次请求,循环执行4次

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9wbVvaK8-1655106882793)(F:\技术积累\Redis学习.assets\image-20220613150334985.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T10rc6ce-1655106882795)(F:\技术积累\Redis学习.assets\image-20220613150546713.png)]

结果展示

从结果上来看,两台服务上有相同的剩余库存,意味着货物ID为46的货物被售卖了两次,出现了超卖的情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOfY351o-1655106882796)(F:\技术积累\Redis学习.assets\image-20220613150828191.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ku2wVAkE-1655106882797)(F:\技术积累\Redis学习.assets\image-20220613150853081.png)]

分布式优化一

采用Redis的SetNX命令

SETNX  key  value 		//存入一个不存在的字符串键值对,如果已经存在就不能设置了

使用该命令,可以很好的使用分布式场景下多线程的竞争,但是加锁容易,删锁就容易出问题,容易出现以下问题。

删锁问题

问题1、如果程序运行逻辑突然失控,陷入死循环,就不能主动删除锁,其他线程就获取不到该锁会一直阻塞住

问题2、如果程序运行中,系统突然宕机,也无法主动删除锁

解决方法

针对问题1,可以在程序逻辑执行中添加 try、catch、finally 这类异常检测的内容,在finally中添加删除锁的操作,避免程序进入死循环后,其他线程无法拿到锁的情况。

针对问题2,添加锁的过期机制,在系统宕机后,锁自动过期,不影响其他用户获取该锁

实操程序
    @RequestMapping("/deduct_stock_setnx")
    public String deductStockSetnx(){
        //version 2
        String lockKey = "lockKey";
        String clientID = UUID.randomUUID().toString();
        //逻辑块
        try{
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "test"); //相当于 jdeis.setnx(key, value)
            stringRedisTemplate.expire(lockKey, 30 , TimeUnit.SECONDS); //锁过期设置
            if(!result){
                return "error 1001";
            }
   
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if(stock > 0){
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功,剩余库存:" + realStock + "");
            }else{
                System.out.println("扣减失败,库存不足");
            }
            stringRedisTemplate.delete(lockKey);
        }finally { //如果程序跑飞了删除key,但是在分布式环境下,可能出现自己的锁被别人删掉了,因为程序逻辑(锁失效的情况)
            stringRedisTemplate.delete(lockKey);
        }
        return "end";
    }

分布式优化二

上述程序还存在问题

问题

1、如果系统在程序刚加完锁后,就立马宕机,意味着还来不及设置过期机制,其他程序也获取不当该锁

2、如果程序的执行时间过长,超过了锁失效的时间,可能会出现锁失效的问题。锁失效具体是指,线程1执行时间过长,导致锁已经过期,这时候线程2获取到锁并运行程序,但是此时线程1执行结束主动释放锁,但是此时是线程2上的锁,因此线程3就会获取锁,从而导致了锁失效问题,配上我拙劣的图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8VsBFqGT-1655106882799)(F:\技术积累\Redis学习.assets\image-20220613154416749.png)]

解决方法

1、问题1的解决方法,是将获取锁指令和设置过期操作的指令和二为一,变成一条指令就具有原子性。

set key value [expiration EX seconds|PX milliseconds] [NX|XX]
参数说明:
EX seconds:将键的过期时间设置为 seconds 秒。
	SET key value EX seconds 等同于 SETEX key seconds value
PX millisecounds:将键的过期时间设置为 milliseconds 毫秒。
	SET key value PX milliseconds 等同于 PSETEX key milliseconds value
NX:只在键不存在的时候,才对键进行设置操作。
	SET key value NX 等同于 SETNX key value
XX:只在键已经存在的时候,才对键进行设置操作

2、问题2的解决方法,是将获取的锁Value设置为当前客户端的ID,每次主动删除的时候先判断是否为自身的锁,如果不是自己加的锁就不能删除。除了这一种解决方案还有锁续命的办法,每次程序执行一段时间会自己是否还持有锁,如果持有就给锁续上时间。

实操程序
    @RequestMapping("/deduct_stock_setnx")
    public String deductStockSetnx(){
        //version 2
        String lockKey = "lockKey";
        String clientID = UUID.randomUUID().toString();
        //逻辑块
        try{
            //Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "test"); //相当于 jdeis.setnx(key, value)
            //stringRedisTemplate.expire(lockKey, 30 , TimeUnit.SECONDS);
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientID, 10, TimeUnit.SECONDS);//相当于set key value [expiration EX seconds|PX milliseconds] [NX|XX]
            if(!result){
                return "error 1001";
            }
            //解决方案: 锁续命
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if(stock > 0){
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功,剩余库存:" + realStock + "");
            }else{
                System.out.println("扣减失败,库存不足");
            }
            stringRedisTemplate.delete(lockKey);
        }finally { //如果程序跑飞了删除key,但是在分布式环境下,可能出现自己的锁被别人删掉了,因为程序逻辑(锁失效的情况)
//            stringRedisTemplate.delete(lockKey);
            //添加这句,表示只删除自身的锁
            if(clientID.equals(stringRedisTemplate.opsForValue().get(lockKey))){
                stringRedisTemplate.delete(lockKey);
            }
        }
        return "end";
    }

分布式优化三

采用无敌工具Redission,其底层是利用lua脚本实现的,Redission就包含锁续命的过程,使用起来十分方便。

@RequestMapping("/deduct_stock")
    public String deductStock() {
        String lockKey = "product_101";
        String clientId = UUID.randomUUID().toString();
        RLock redissonLock = redisson.getLock(lockKey); //redisson 获取锁对象
        try {
            
            //加锁,实现续命操作
            redissonLock.lock();  //setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败,库存不足");
            }

        } finally {
            //解锁命令
            redissonLock.unlock();
            /*if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete(lockKey);
            }*/
        }

运行程序在我的资源中下载

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

专题五 Redis高并发场景 的相关文章

随机推荐

  • C++学习--cin不支持录入空格

    https blog csdn net EXLsunshine article details 28440629 举个栗子 当使用cin功能然后键盘输入 aaa bbb ccc 时 cin的那个字符串只会保留 aaa
  • Apache解析漏洞

    Apache解析漏洞主要是因为Apache默认一个文件可以有多个用 分割得后缀 当最右边的后缀无法识别 mime types文件中的为合法后缀 则继续向左看 直到碰到合法后缀才进行解析 以最后一个合法后缀为准 1 如图 最后一个后缀名为 x
  • IPC:消息队列

    消息队列 gcc messageQueue c o mq lrt mqd t mq send mqd t mqdes const char msg ptr size t msg len unsigned msg prio msg prio
  • OpenCV 实现读取摄像头、视频读取保存 (C++)

    一 读取摄像头 重点语句 VideoCapture imshow 原理 使用VideoCapture语句读取摄像头 再利用while一次次将VideoCapture所读取的数据利用imshow语句一帧帧地读取出来 include
  • 基于多任务学习和GCN的交通路网出租车需求预测

    1 文章信息 文章题目为 Multitask Learning and GCN Based Taxi Demand Prediction for a Traffic Road Network 是一篇发表在Sensors期刊上的有关基于多任务
  • 解决vista和win10在windows服务中交互桌面权限问题:穿透Session 0 隔离

    服务 Service 对于大家来说一定不会陌生 它是Windows 操作系统重要的组成部分 我们可以把服务想像成一种特殊的应用程序 它随系统的 开启 关闭 而 开始 停止 其工作内容 在这期间无需任何用户参与 Windows 服务在后台执行
  • python数据分析面试_python数据分析面试

    1 如何利用SciKit包训练一个简单的线性回归模型 利用linear model LinearRegression 函数 Create linear regression object regr linear model LinearRe
  • vs code配置c语音环境

    要在VS Code中配置C语言环境 您可以按照以下步骤进行操作 安装C C 扩展程序 在VS Code中 您可以通过搜索 C C 来找到C C 扩展程序 安装该扩展程序后 您可以在VS Code中使用C C 语言编写代码 创建C C 项目
  • C++入门-----拷贝构造

    学习目标 1 拷贝构造函数的概念及使用 2 特征 3 注意的点 3 1 防止无穷递归 3 2 防止原对象被修改 4 默认生成拷贝构造 4 1 浅拷贝 4 2 为什么要自己实现拷贝构造函数 4 3 其对于内置类型和自定义类型的处理方式 5 总
  • vue响应式数据-修改对象的属性值,视图不更新

    目录 bug复现 原因 怎么在console中判断是不是响应式数据 怎样才能设置为响应式数据 bug复现 在代码中给一个对象 新增多个属性并赋值 然后在另一个方法中修改对象其中一个属性的值 发现数据改变 页面视图并没有更新 data ret
  • 在Vue.js的public目录下的index.html文件中,可以使用EJS(Embedded JavaScript)模板语法来插入变量。

    这个示例中 和被用于插入对应的变量 这些变量将被EJS模板引擎根据环境和配置进行替换 同样 会将htmlWebpackPlugin插件的options title属性插入到
  • vue中动态水印

    效果 一 根元素div上增加 div class rootZhy div 二 增加方法 watermarkZhy content let container document body let width 120px let height
  • BeanCreationException: Error creating bean with name ‘configurationPropertiesBeans‘ defined in class

    1 现象 启动 SpringCloud项目时报错 2 解决方案 2 1 spring cloud dependencies添加到dependencyManagement标签
  • STM32开发(六)STM32F103 通信 —— RS485 Modbus通信编程详解

    上一篇 主目录 下一篇 文章目录 一 基础知识点 二 开发环境 三 STM32CubeMX相关配置 1 STM32CubeMX基本配置 2 STM32CubeMX RS485 相关配置 四 Vscode代码讲解 五 结果演示以及报文解析 六
  • Spring Boot 添加拦截器

    文章目录 Spring Boot 添加拦截器 方法1 新增拦截器 配置拦截器 方法2 新增拦截器 配置拦截器 拦截所有响应 Spring Boot 添加拦截器 介绍一下在Spring Boot 2 0 0以上版本如何添加拦截器 方法1 新增
  • html烟花代码在线编程,canvas实现烟花的示例代码

    前言 马上过年了 我打算在后台里面偷偷地埋个新春祝福 放烟花的彩蛋 项目是基于react typescript的 因此最后封装成了一个组件 设置好开启时间就可以显示了 目录结构 目录结构大致如下 我们将烟花分为两个阶段 一个是未炸开持续上升
  • Python不同excel的合并操作

    Python不同excel的合并操作 23333333333我的第一篇博客 有一点瞎搞的感觉 0 0 问题描述 对7个类似下图的Excel进行合并 合并的最终效果 Excel文件需要可以找我 技术分析 获取表格中相同格式的部分进行操作 对除
  • 华为OD机试真题 Java 实现【服务中心选址】【2023Q1 100分 】

    一 题目描述 一个快递公司希望在一条街道建立新的服务中心 公司统计了该街道中所有区域在地图上的位置 并希望能够以此为依据为新的服务中心选址 使服务中心到所有区域的距离的总和最小 给你一个数组 positions 其中 positions i
  • 苏宁图书爬虫第一版

    最近一直在忙于工作 当我知道这些都是借口 毕竟某些博主大佬深夜还在更新订阅号更新微博 或许这就是自制力的差距吧 不啰嗦了 今天要写的主要是一篇关于如何爬取 苏宁图书 当然只是半成品 但是大部分问题都已经解决 在这里记录一下发生过的问题 以免
  • 专题五 Redis高并发场景

    介绍 Redis高并发场景 如果直接去学会比较抓不住头绪 因此本文将一步步介绍Redis的高并发的步骤演进 首先解释synchronized不适合在分布式场景 因为synchronized只适用自身的JVM 因此在分布式场景下多台机器的情况