Redis学习之四----分布式锁

今天记录学习Redis分布式的相关功能。
在公司写业务代码的时候,遇到超卖的问题,接下来就慢慢的探讨这个问题的解决方案。
在单体的公司中,我们可以使用synchronized解决相关问题。代码如下:
一:使用synchronized

    @RequestMapping(value = "buy_Goods1", method = RequestMethod.GET)
    public String buy_Goods1() {
        synchronized (this) {
            String key = "lock:good_101";
            String resultRedisTotal = (String) redisTemplate.opsForValue().get(key);
            Integer totalNum = Integer.parseInt(resultRedisTotal);
            if (totalNum > 0) {
                int realNum = totalNum - 1;
                redisTemplate.opsForValue().set(key, String.valueOf(realNum));
                return "购买成功" + realNum;
            } else {
                System.out.println("商品已经售完");
            }
            return "商品已经售完";
        }
    }

随着微服务的兴起,上述代码在微服务就无法完成该功能了。于是我们就想到了redis可以实现分布式锁的功能。利用它内部的setNx命令
二:使用分布式锁

    @RequestMapping(value = "buy_Goods2", method = RequestMethod.GET)
    public String buy_Goods2() {
        // 分布式锁
        String value = Thread.currentThread().getName();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);// 相当于setNx
        if (!flag) {
            return "抢锁失败";
        }
        String key = "lock:good_101";
        String resultRedisTotal = (String) redisTemplate.opsForValue().get(key);
        Integer totalNum = Integer.parseInt(resultRedisTotal);
        if (totalNum > 0) {
            int realNum = totalNum - 1;
            redisTemplate.opsForValue().set(key, String.valueOf(realNum));
            redisTemplate.delete(REDIS_LOCK);
            return "购买成功";
        } else {
            System.out.println("商品已经售完");
        }
        return "商品已经售完";
    }

以上就会发现一个问题,如果在执行业务代码报错了,没有执行到delete删除锁的时候,就会出现该锁一直在的问题。
三:程序报错也最终解锁

  @RequestMapping(value = "buy_Goods3", method = RequestMethod.GET)
    public String buy_Goods3() {
        // 分布式锁
        String value = Thread.currentThread().getName();
        try {
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);// 相当于setNx
            if (!flag) {
                return "抢锁失败";
            }
            String key = "lock:good_101";
            String resultRedisTotal = (String) redisTemplate.opsForValue().get(key);
            Integer totalNum = Integer.parseInt(resultRedisTotal);
            if (totalNum > 0) {
                int realNum = totalNum - 1;
                redisTemplate.opsForValue().set(key, String.valueOf(realNum));
                return "购买成功";
            } else {
                System.out.println("商品已经售完");
            }

        } catch (Exception e) {
        } finally {
            redisTemplate.delete(REDIS_LOCK);
        }
        return "商品已经售完";
    }

以上改造,依旧会存在一个问题,如果该程序被kill掉,那么在redis的锁还会一直在,为了解决该问题,我们也需要设置redis的过期时间
四:设置redis锁的过期时间

    @RequestMapping(value = "buy_Goods4", method = RequestMethod.GET)
    public String buy_Goods4() {
        // 分布式锁
        String value = Thread.currentThread().getName();
        try {
            // 原子性
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 5, TimeUnit.SECONDS);// 相当于setNx
            if (!flag) {
                return "抢锁失败";
            }

            String key = "lock:good_101";
            String resultRedisTotal = (String) redisTemplate.opsForValue().get(key);
            Integer totalNum = Integer.parseInt(resultRedisTotal);
            if (totalNum > 0) {
                int realNum = totalNum - 1;
                redisTemplate.opsForValue().set(key, String.valueOf(realNum));
                return "购买成功";
            } else {
                System.out.println("商品已经售完");
            }

        } catch (Exception e) {

        } finally {
            redisTemplate.delete(REDIS_LOCK);
        }
        return "商品已经售完";
    }

再次改造后,还是会发现一个问题。就是在删除锁的时候,如果A进行卡顿了,被redis过期时间删除,B线程进入代码,在finally中,就会被删掉。
五:防止删除别的线程的锁

    @RequestMapping(value = "buy_Goods5", method = RequestMethod.GET)
    public String buy_Goods5() {
        // 分布式锁
        String value = Thread.currentThread().getName();
        try {
            // 原子性
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 5, TimeUnit.SECONDS);// 相当于setNx
            if (!flag) {
                return "抢锁失败";
            }
            String key = "lock:good_101";
            String resultRedisTotal = (String) redisTemplate.opsForValue().get(key);
            Integer totalNum = Integer.parseInt(resultRedisTotal);
            if (totalNum > 0) {
                int realNum = totalNum - 1;
                redisTemplate.opsForValue().set(key, String.valueOf(realNum));
                return "购买成功";
            } else {
                System.out.println("商品已经售完");
            }

        } catch (Exception e) {

        } finally {
            // 解锁
            if (redisTemplate.opsForValue().get(REDIS_LOCK).equals(value)) {
                redisTemplate.delete(REDIS_LOCK);
            }

        }
        return "商品已经售完";
    }

经过以上改造,我们又会发现一个问题,finally块的代码和del的删除操作不是一个原子性的,依旧会出现问题。
六:解决finally块和删除操作的原子性(使用lua脚本)
众所周知,lua脚本是可以实现原子性的操作的。

 String script = "if redis.call('get',KEYS[1]) == ARGV[1]" +
                  "then " +
                  "return redis.call('del',KEYS[1])" +
                  "else" +
                  "  return 0" +
                  "end";

另一种方式,使用redis本事的事务特性,完成该操作。

            while (true){
                redisTemplate.watch(REDIS_LOCK);
                if(redisTemplate.opsForValue().get(REDIS_LOCK).equals(value)){
                    redisTemplate.setEnableTransactionSupport(true);
                    redisTemplate.multi();
                    redisTemplate.delete(REDIS_LOCK);
                    List<Object> list = redisTemplate.exec();
                    if(list == null){
                        continue;
                    }

                }
                redisTemplate.unwatch();
                break;
            }

以上的改造,我们就可以基本上完成一个分布式锁的超卖问题。

随着Redis的发展,给我们封装了更好的解决方案。解决了Redis中的缓存续命问题。
七:使用Redisson

    @RequestMapping(value = "buy_Goods7", method = RequestMethod.GET)
    public String buy_Goods7() {
        String key = "lock:good_101";
        String value = Thread.currentThread().getName();
        RLock redissonLock =  redisson.getLock(REDIS_LOCK);
         redissonLock.lock();
        try{
            String resultRedisTotal = (String) redisTemplate.opsForValue().get(key);
            Integer totalNum = Integer.parseInt(resultRedisTotal);
            if (totalNum > 0) {
                int realNum = totalNum - 1;
                redisTemplate.opsForValue().set(key, String.valueOf(realNum));
                return "购买成功" + realNum;
            } else {
                System.out.println("商品已经售完");
            }
        }finally {
            redissonLock.unlock();;
        }

        return "商品已经售完";
    }

以上的Redisson自己封装了Lua脚本的解决方案,对我们业务使用者来说,三行代码即可。

以上还是在大量高并发的情况下,还是会有一些问题的存在,可能会报错:attempt to unlock lock,not locked by current thread by node id;
八:使用Redission解决解锁的时候高并发问题

    @RequestMapping(value = "buy_Goods8", method = RequestMethod.GET)
    public String buy_Goods8() {
        String key = "lock:good_101";
        String value = Thread.currentThread().getName();
        RLock redissonLock =  redisson.getLock(REDIS_LOCK);
        redissonLock.lock();
        try{
            String resultRedisTotal = (String) redisTemplate.opsForValue().get(key);
            Integer totalNum = Integer.parseInt(resultRedisTotal);
            if (totalNum > 0) {
                int realNum = totalNum - 1;
                redisTemplate.opsForValue().set(key, String.valueOf(realNum));
                return "购买成功" + realNum;
            } else {
                System.out.println("商品已经售完");
            }
        }finally {
            // 解锁  避免报错  attempt to unlock lock,not locked by current thread by node id;
            if(redissonLock.isLocked()){
                if(redissonLock.isHeldByCurrentThread()){
                    redissonLock.unlock();
                }
            }
        }

        return "商品已经售完";
    }

以上就是在使用Redis作为分布式锁的过程中,一步步进行项目的优化,处理对应的问题,找到最合适的分布式锁。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

virtuousOne

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值