• 为什么使用Redission解决高并发场景分布式锁问题


    业务场景:高并发场景下的减库存代码实现

    方案一:使用JVM或JDK级别的锁【synchronized】

    问题:使用synchronized的加锁,如果是单机环境的话没有问题,但是对于集群/分布式环境则会出问题,对于跨tomcat就会锁不住。

    @RestController
    public class IndexControlelr {
    
        @Autowired
        private Redisson redisson;
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @RequestMapping("/deduct_stock")
        public String deductStock() {
      
            //以下的代码高并发场景下有问题
            synchronized(this) {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));   //获取redis值  jedis.setnx(key.value)
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("", realStock + "");   //设置redis值   jedis.set(key.value)
                Sysytem.out.printLn("扣减成功,剩余库存:" + realStock);
            } else {
                Sysytem.out.printLn("扣减失败,库存不足");
            }
          }
            return "end";
        }
    }

    方案二:为了解决方案一的问题,使用redis分布式锁的SETNX命令可以解决刚刚方案一的问题。

    使用格式:setnx key value   将key的值设为value,当且仅当key不存在。若给定的key已存在,则SETNX不做任何操作。

    问题:会出现死锁,就是当程序执行一般,中间的代码出现异常导致无法释放这把锁,此时就会出现死锁的现象。

         //使用redis分布式锁   
        @RequestMapping("/deduct_stock")
        public String deductStock() {
        
            String lockKey = "lockKey";
            boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"xl");
            if(!result){
                return "error_code";
            }
    //如果执行到这里下面的代码抛异常则无法完成释放锁,死锁产生
    int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); //获取redis值 jedis.setnx(key.value) if (stock > 0) { int realStock = stock - 1; stringRedisTemplate.opsForValue().set("", realStock + ""); //设置redis值 jedis.set(key.value) Sysytem.out.printLn("扣减成功,剩余库存:" + realStock); } else { Sysytem.out.printLn("扣减失败,库存不足"); } //释放锁 stringRedisTemplate.delete(lockKey); return "end"; }

    方案三:为了解决方案二的问题,设置key和操作时间+try ...catch...finally释放锁

    问题:时间问题,高并发场景下此代码将就可以用了,但是会出现自己加的锁会被别人释放掉。

        @RequestMapping("/deduct_stock")
        public String deductStock() {
            
            String lockKey = "lockKey";
            
            try {
            //解决:给锁加一个超时时间,
            //boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"xl");
            //stringRedisTemplate.expire(lockKey,10,TimeUnit.SECONDS);
            //设置key和操作时间==保证原子性 将上面的两行合并为下面的一行
            boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"xl",10,TimeUnit.SECONDS);
            
            if(!result){
                return "error_code";
            }
            
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));   //获取redis值  jedis.setnx(key.value)
            if (stock > 0) {
                int realStock = stock - 1; 
                stringRedisTemplate.opsForValue().set("", realStock + "");   //设置redis值  jedis.set(key.value)
                Sysytem.out.printLn("扣减成功,剩余库存:" + realStock);
            } else {
                Sysytem.out.printLn("扣减失败,库存不足");
            }
            //解决释放锁的问题使用finally释放锁
            }funally {
            //释放锁
            stringRedisTemplate.delete(lockKey);
          }
            return "end";
        }

    方案四:为了解决方案三的问题,生成UUID,将这个UUID设置到锁对应的value里面,自己加的锁只能自己释放,并在后台启动一个分线程每个一段时间(一般这个时间是你设置的超时时间的1/3,超时时间一般默认为30s)去检查一下主线程是否还持有这把锁,如果还持有这把锁的话就把设置的超时时间顺延30s

    问题:代码量太大了

        @RequestMapping("/deduct_stock")
        public String deductStock() {
            
            String lockKey = "lockKey";
            //生成一个UUID
            String clientId = UUID.randomUUID().toString();
            
            try {
            //boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"xl");
            //stringRedisTemplate.expire(lockKey,10,TimeUnit.SECONDS);
            //将UUID设置到锁对应的value里面
            //这里的时间还是会存在问题==解决,在后台起一个分线程:起一个定时任务每10s(不超过30s,设置值的1/3时间)检查一下主线程是否还持有这把锁,
            //如果还持有把这个超时时间延长(重新设置30s),====>问题代码量太大了
            
            //解决方案:reddison框架底层的原理就是我们现在写的这些逻辑。
            //使用:pom直接引入依赖包即可,可以支持很多redis架构模式(主从,哨兵,高并发等)
            boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,clientId,30,TimeUnit.SECONDS);
            
            if(!result){
                return "error_code";
            }
            
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));   //获取redis值  jedis.setnx(key.value)
            if (stock > 0) {
                int realStock = stock - 1; 
                stringRedisTemplate.opsForValue().set("", realStock + "");   //设置redis值  jedis.set(key.value)
                Sysytem.out.printLn("扣减成功,剩余库存:" + realStock);
            } else {
                Sysytem.out.printLn("扣减失败,库存不足");
            }
            //解决释放锁的问题使用finally释放锁
            }funally {
            //判断一下这把锁是不是自己加的锁(线程id)
            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
                stringRedisTemplate.delete(lockKey);
            }
          }
            return "end";
        }
        
    }

    方案五:为了解决方案四的问题。使用Redission框架

    @SpringBootApplication
    public class Application {
    
         public static void main(String[] args) {
         SpringApplication.run(Application.class,args);
         }
         
         @Bean
         public Redisson redisson() {
             Config config = new Config();
             config.useSingleServer().setAddress("redis://localhost:8769").setDatabase(0);
             return (Redisson) Redisson.create(config);
         }
    }
    
    -----------------------------------------------------------------------------------------------------
    @RequestMapping("/deduct_stock")
        public String deductStock() {
            
            String lockKey = "lockKey";
    
            RLock redissonLock = redisson.getLock(lockKey);
            
            try {
                //加锁默认的超时时间30s
                redissonLock.lock();   //setIfAbsent(lockKey,clientId,30,TimeUnit.SECONDS);
                
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));   //获取redis值  jedis.setnx(key.value)
                if (stock > 0) {
                    int realStock = stock - 1; 
                    stringRedisTemplate.opsForValue().set("", realStock + "");   //设置redis值  jedis.set(key.value)
                    Sysytem.out.printLn("扣减成功,剩余库存:" + realStock);
                } else {
                Sysytem.out.printLn("扣减失败,库存不足");
            }
        }funally {
             
            //释放锁
            redissonLock.unlock();
            }
        }
        return "end";
    }

    Redission 的源码剖析

     Lua脚本具有原子性,所以redis把上面的一整串字符串当作一条命令来执行,要么成功,要么失败(解决了分布式一致性的问题)。

    Redission分布式锁实现原理

    Redis集群架构一般是满足AP,redis主节点获取到锁之后会立马返回给客户端。QPS理论上是10万,但是一般达不到10万,才几万。

    zookeeper是CP:一致性,主节点获取到锁之后先同步给从节点,半数以上的从节点获取到锁之后再返回给客户端。

    ****一般会使用zookeeper来做分布式锁,redis做缓存,但是还是有很多的公司选择使用redis来做分布式锁,因为redis的性能高(对并发要求比较高的情况)。

  • 相关阅读:
    git 同步远程分支
    git tag 打标签
    EJS 语法
    从零开始制作 Hexo 主题
    博客灵感
    java编译做了哪些事?
    java+内存分配及变量存储位置的区别[转]
    用android模拟器Genymotion定位元素
    利用securecrt在linux与windows之间传输文件
    eclipse引入tomcat
  • 原文地址:https://www.cnblogs.com/nastu/p/15645645.html
Copyright © 2020-2023  润新知