基于Redis和Redisson的分布式锁的实现

一、环境搭建

在redis服务器存储了一个key,值为100

在这里插入图片描述
Java业务代码,业务逻辑为如果redis中的key值大于0,则减1并更新

@GetMapping("reduce/stock")
    public String reduceStock(){
        String stock = redisTemplate.opsForValue().get("stock");
        if (!StringUtils.isEmpty(stock)){
            Integer stock_num = Integer.parseInt(stock);
            if (stock_num > 0){
                stock_num = stock_num - 1;
                redisTemplate.opsForValue().set("stock",String.valueOf(stock_num));
                System.out.println("扣减成功,余额为" + stock_num);
                return "扣减成功,余额为" + stock_num;
            }else {
                System.out.println("库存不足");
                return "库存不足";
            }
        }else {
            return "error";
        }
    }

二、压力测试

使用Jmeter模拟多线程环境下的库存资源问题。可参考该篇文章使用JMeter创建多线程环境。
JMeter的使用
在这里插入图片描述
这种问题如果是单体应用,则我们添加synchronized关键字即可。但如果项目是集群的话,就会有多个项目独立于不同的JVM进程之间,此时单单使用synchronized关键字也是不行的,这就引入了分布式锁的概念,即使在不同的JVM进程中也能保证某一时刻只有一个线程获取某个资源。

三、Redis方式实现

一、判断某个key是否在redis中存在

    @GetMapping("reduce/stock")
    public  String reduceStock(){

        //setIfAbsent 如果不存在则设置lockKey,返回true,如果存在lockKey,则放弃更新,返回false
        Boolean success = redisTemplate.opsForValue().setIfAbsent("lockKey", UUID.randomUUID().toString());
        if (!success){
            //表示key存在,此时已经被某个线程操作
            return "Being operated by another thread";
        }

        String stock = redisTemplate.opsForValue().get("stock");
        if (!StringUtils.isEmpty(stock)){
            Integer stock_num = Integer.parseInt(stock);
            if (stock_num > 0){
                stock_num = stock_num - 1;
                redisTemplate.opsForValue().set("stock",String.valueOf(stock_num));
                System.out.println("扣减成功,余额为" + stock_num);
            }else {
                System.out.println("库存不足");
            }
        }else {
            System.out.println("error");
        }
        //释放锁,也就是删除key
        redisTemplate.delete("lockKey");
        return "success";
    }

此时的代码不需要synchronized关键字,因为redis就是单线程的,保证只会有一个线程执行redisTemplate.opsForValue().setIfAbsent(“lockKey”, UUID.randomUUID().toString());

二、防止程序出现异常而增加finally语句块
将上面代码升级,防止程序出现异常而没有删除key,造成死锁。

 @GetMapping("reduce/stock")
    public  String reduceStock(){
        String lockKey = "lockKey";
       try {
           //setIfAbsent 如果不存在则设置lockKey,返回true,如果存在lockKey,则放弃更新,返回false
           Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, UUID.randomUUID().toString());
           if (!success){
               //表示key存在,此时已经被某个线程操作
               return "Being operated by another thread";
           }
           String stock = redisTemplate.opsForValue().get("stock");
           if (!StringUtils.isEmpty(stock)){
               Integer stock_num = Integer.parseInt(stock);
               if (stock_num > 0){
                   stock_num = stock_num - 1;
                   redisTemplate.opsForValue().set("stock",String.valueOf(stock_num));
                   System.out.println("扣减成功,余额为" + stock_num);
               }else {
                   System.out.println("库存不足");
               }
           }else {
               System.out.println("error");
           }
       }finally {
           //释放锁,也就是删除key
           redisTemplate.delete("lockKey");
       }
        return "success";
    }

三、此时如果在执行try语句块的过程中集群中的某个应用突然宕机,则无法执行finally语句块的内容,依然造成了死锁
解决:为key设置过期时间,超过该时间若没有删除,则由redis服务器删除

@GetMapping("reduce/stock")
    public  String reduceStock(){
        String lockKey = "lockKey";
       try {
           //setIfAbsent 如果不存在则设置lockKey,返回true,如果存在lockKey,则放弃更新,返回false
           Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, UUID.randomUUID().toString(),30, TimeUnit.SECONDS);
           if (!success){
               //表示key存在,此时已经被某个线程操作
               return "Being operated by another thread";
           }
           String stock = redisTemplate.opsForValue().get("stock");
           if (!StringUtils.isEmpty(stock)){
               Integer stock_num = Integer.parseInt(stock);
               if (stock_num > 0){
                   stock_num = stock_num - 1;
                   redisTemplate.opsForValue().set("stock",String.valueOf(stock_num));
                   System.out.println("扣减成功,余额为" + stock_num);
               }else {
                   System.out.println("库存不足");
               }
           }else {
               System.out.println("error");
           }
       }finally {
           //释放锁,也就是删除key
           redisTemplate.delete("lockKey");
       }
        return "success";
    }

四、过期时间的问题,假设给某个key设置的过期时间为15s,其执行业务代码假设需要20s,也就是说,当该线程还没有全部执行完该代码时,redis服务器就把key删除了,此时第二个线程进来,又设置了该key,假设第二个线程执行代码的时间为17s,当第二个线程在执行代码的过程中,第一个线程把第二个线程设置的key给删了,那此时又有第三个线程进来,这样就造成在高并发的情况下依然锁不住资源的问题
解决:每个线程设置一个特定的value,在删除该key,判断是不是自己的value,如果相等,则删除

 @GetMapping("reduce/stock")
    public  String reduceStock(){
        String lockKey = "lockKey";
        String value = UUID.randomUUID().toString();
       try {
           //setIfAbsent 如果不存在则设置lockKey,返回true,如果存在lockKey,则放弃更新,返回false
           Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, value,30, TimeUnit.SECONDS);
           if (!success){
               //表示key存在,此时已经被某个线程操作
               return "Being operated by another thread";
           }
           String stock = redisTemplate.opsForValue().get("stock");
           if (!StringUtils.isEmpty(stock)){
               Integer stock_num = Integer.parseInt(stock);
               if (stock_num > 0){
                   stock_num = stock_num - 1;
                   redisTemplate.opsForValue().set("stock",String.valueOf(stock_num));
                   System.out.println("扣减成功,余额为" + stock_num);
               }else {
                   System.out.println("库存不足");
               }
           }else {
               System.out.println("error");
           }
       }finally {
           String currentValue = redisTemplate.opsForValue().get(lockKey);
           if (value.equals(currentValue)){
               //释放锁,也就是删除key
               redisTemplate.delete("lockKey");
           }
       }
        return "success";
    }

五、异步任务解决key由于超时被redis删除的问题

 @GetMapping("reduce/stock")
    public  String reduceStock(){
        String lockKey = "lockKey";
        String value = UUID.randomUUID().toString();
        Long exprieTime = 30l;
       try {
           //setIfAbsent 如果不存在则设置lockKey,返回true,如果存在lockKey,则放弃更新,返回false
           Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, value,exprieTime, TimeUnit.SECONDS);
           if (!success){
               //表示key存在,此时已经被某个线程操作
               return "Being operated by another thread";
           }
           isExpire(lockKey,exprieTime);
           String stock = redisTemplate.opsForValue().get("stock");
           if (!StringUtils.isEmpty(stock)){
               Integer stock_num = Integer.parseInt(stock);
               if (stock_num > 0){
                   stock_num = stock_num - 1;
                   redisTemplate.opsForValue().set("stock",String.valueOf(stock_num));
                   System.out.println("扣减成功,余额为" + stock_num);
               }else {
                   System.out.println("库存不足");
               }
           }else {
               System.out.println("error");
           }
       }finally {
           String currentValue = redisTemplate.opsForValue().get(lockKey);
           if (value.equals(currentValue)){
               //释放锁,也就是删除key
               redisTemplate.delete("lockKey");
           }
       }
        return "success";
    }

   @Async
    public void isExpire(String key,Long expireTime){
        while (true){
            String value = redisTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(value)){
                //延长key的时间
                Long leftTime = redisTemplate.opsForValue().getOperations().getExpire(key);
                Long addTime = expireTime / 3;
                leftTime = leftTime + addTime;
                redisTemplate.expire(key,leftTime,TimeUnit.SECONDS);
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                return;
            }
        }
    }

Redisson方式实现

引入redission依赖

	 <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.12.5</version>
        </dependency>

编写配置类

  @Bean
    public Redisson redisson(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }

使用redisson锁

    @GetMapping("reduce/stock")
    public  String reduceStock(){
        String lockKey = "lockKey";
        //redisson获取锁对象
        RLock lock = redisson.getLock(lockKey);
        try {
            //执行锁
            lock.lock();
           String stock = redisTemplate.opsForValue().get("stock");
           if (!StringUtils.isEmpty(stock)){
               Integer stock_num = Integer.parseInt(stock);
               if (stock_num > 0){
                   stock_num = stock_num - 1;
                   redisTemplate.opsForValue().set("stock",String.valueOf(stock_num));
                   System.out.println("扣减成功,余额为" + stock_num);
               }else {
                   System.out.println("库存不足");
               }
           }else {
               System.out.println("error");
           }
       }finally {
           //redisson释放锁
            lock.unlock();
       }
        return "success";
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值