ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-01-27 01:32:53  阅读:202  来源: 互联网

标签:Map 加锁 return lock redis 缓存 原理 stringRedisTemplate 分布式


 

 

用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 

 

标签:Map,加锁,return,lock,redis,缓存,原理,stringRedisTemplate,分布式
来源: https://www.cnblogs.com/wyw123456/p/15848581.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有