Java教程

缓存:分布式锁原理与使用

本文主要是介绍缓存:分布式锁原理与使用,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

 

 

用redis的 set   nx 命令。

 

 

 

阶段一  ,代码改进:

  @Override
    public Map<String, List<Catelog2Vo>> getCatelogJson() {
        //加入缓存逻辑
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String json = ops.get("CatalogJSON");
        if (StringUtils.isEmpty(json)) {
            //缓存没有,从数据库中查询
//            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbWithLocalLock();
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbWithRedisLock();
            return catalogJsonFromDb;
        }

        //视频中是这样转然后返回的
//        Map<String, List<Catelog2Vo>> object
//                = JSON.parseObject(json, new TypeReference<Map<String, List<Catelog2Vo>>>() {});

        return (Map<String, List<Catelog2Vo>>) JSON.parse(json);
    }



    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

        //占分布式锁,去redis占坑
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "11111");
        if(lock){
            //加锁成功,执行业务
            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
            //释放锁
            stringRedisTemplate.delete("lock");
            return dataFromDb;
        }else{
            //加锁失败  重试  ----自旋的方式
            return getCatalogJsonFromDbWithRedisLock();
        }
    }

如果执行业务的时候宕机了,锁就得不到释放。

=================================

改进,设置过期时间:

 public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

        //占分布式锁,去redis占坑
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "11111");
        if(lock){
            //设置过期时间
            stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS);
            //加锁成功,执行业务
            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
            //释放锁
            stringRedisTemplate.delete("lock");
            return dataFromDb;
        }else{
            //加锁失败  重试  ----自旋的方式
            return getCatalogJsonFromDbWithRedisLock();
        }
    }

但是如果,刚进 if  ,过期时间还没设置,就宕机了,同样也会死锁。

加锁和设置过期时间,如果是原子操作,就能解决。

 

 

 

 再次改进代码(阶段2):

 public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

        //占分布式锁,去redis占坑  +设置过期时间
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "11111",30,TimeUnit.SECONDS);
        if(lock){
            //设置过期时间,没保证和加锁一起成为原子操作
//            stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS);
            //加锁成功,执行业务
            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
            //释放锁
            stringRedisTemplate.delete("lock");
            return dataFromDb;
        }else{
            //加锁失败  重试  ----自旋的方式
            return getCatalogJsonFromDbWithRedisLock();
        }
    }

 

====================================

 

 

 

 

删除锁的时候,如果业务执行时间长,锁已经过期了,这时候再删除就是别人的锁了。解决:+UUID保证删的是自己的锁

阶段3,代码再次改进:

public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
        String uuid = UUID.randomUUID().toString();

        //占分布式锁,去redis占坑  +设置过期时间
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);
        if(lock){
            //设置过期时间,没保证和加锁一起成为原子操作
//            stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS);
            //加锁成功,执行业务
            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
            //释放锁
            if(stringRedisTemplate.opsForValue().get("lock").equals(uuid)){
                stringRedisTemplate.delete("lock");
            }
            return dataFromDb;
        }else{
            //加锁失败  重试  ----自旋的方式
            return getCatalogJsonFromDbWithRedisLock();
        }
    }

 

==========================

 

 删锁的问题。。解决方法就是用lua脚本完成。

 

 

 

 阶段四再次改进后:

public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
        String uuid = UUID.randomUUID().toString();

        //占分布式锁,去redis占坑  +设置过期时间
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);
        if(lock){
            //设置过期时间,没保证和加锁一起成为原子操作
//            stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS);
            //加锁成功,执行业务
            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
            //释放锁 仍存在问题,应该用lua脚本删锁
//            if(stringRedisTemplate.opsForValue().get("lock").equals(uuid)){
//                stringRedisTemplate.delete("lock");
//            }
            //lua脚本解锁
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            //删除锁  返回值就是执行redis命令的返回值
            Integer execute = stringRedisTemplate.execute(
                    new DefaultRedisScript<Integer>(script, Integer.class)
                    , Arrays.asList("lock")
                    , uuid);
            
            return dataFromDb;
        }else{
            //加锁失败  重试  ----自旋的方式
            return getCatalogJsonFromDbWithRedisLock();
        }
    }

 

=================================

 

 

==============================

如果业务时间超级长,锁过期了,就要对锁进行续期。

最简单的解决方案,就是把锁的时间弄长一些。

上面代码还存在问题,业务异常呢?执行业务的时候,出现异常,锁没释放?

再次改进,finally 中释放锁,永远执行。:

 public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
        String uuid = UUID.randomUUID().toString();

        //占分布式锁,去redis占坑  +设置过期时间
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
        if (lock) {
            Map<String, List<Catelog2Vo>> dataFromDb;
            try {
                //设置过期时间,没保证和加锁一起成为原子操作
//            stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS);
                //加锁成功,执行业务
                dataFromDb = getDataFromDb();
            } finally {
                //释放锁 仍存在问题,应该用lua脚本删锁
//            if(stringRedisTemplate.opsForValue().get("lock").equals(uuid)){
//                stringRedisTemplate.delete("lock");
//            }
                //lua脚本解锁
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                //删除锁  返回值就是执行redis命令的返回值
                Integer execute = stringRedisTemplate.execute(
                        new DefaultRedisScript<Integer>(script, Integer.class)
                        , Arrays.asList("lock")
                        , uuid);
            }
            return dataFromDb;
        } else {
            //加锁失败  重试  ----自旋的方式
            return getCatalogJsonFromDbWithRedisLock();
        }
    }

我没运行,视频 中运行出现错误,解锁那里,integer不能接受应该换long 

 

这篇关于缓存:分布式锁原理与使用的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!