IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 专题五 Redis高并发场景 -> 正文阅读

[大数据]专题五 Redis高并发场景

介绍

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);
            }*/
        }

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

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-06-18 23:28:02  更:2022-06-18 23:29:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/17 17:25:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码