缓存4-分布式锁的原理与使用

为什么使用分布式锁?

上一讲缓存穿透、雪崩、击穿讲到本地锁 synchronized (this) ,JUC(lock)在单体、分布式下进行jmeter压力测试,本地锁在单体应用可以锁住所有服务,但是分布式下不能锁住所有服务。怎么办呢?

一、分布式锁

1、分布式锁与本地锁

在这里插入图片描述

2.分布式锁演进-基本原理

在这里插入图片描述
redis的命令
在这里插入图片描述
语法:SET key value [EX seconds] [PX milliseconds] [NX|XX]
过期时间秒 毫秒 not exist、exist
选项
从2.6.12版本开始,redis为SET命令增加了一系列选项:

-EX seconds – Set the specified expire time, in seconds.
-PX milliseconds – Set the specified expire time, in milliseconds.
-NX – Only set the key if it does not already exist.
-XX – Only set the key if it already exist.

-EX seconds – 设置键key的过期时间,单位时秒
-PX milliseconds – 设置键key的过期时间,单位时毫秒
-NX – 只有键key不存在的时候才会设置key的值
-XX – 只有键key存在的时候才会设置key的值
注意: 由于SET命令加上选项已经可以完全取代SETNX, SETEX, PSETEX的功能,所以在将来的版本中,redis可能会不推荐使用并且最终抛弃这几个命令。

1).测试NX分布式锁

多开几个redis
给所有回话发送,看谁能获得锁
在这里插入图片描述
底部撰写栏,发送(全部xshell)
在这里插入图片描述
在这里插入图片描述

docker exec -it redis redis-cli
set lock haha NX 

结果:只有一个redis可以获得锁,去占坑

2).分布式锁阶段1

在这里插入图片描述

a. 代码

在这里插入图片描述

    //2.获取二级、三机分类(从数据库查询并封装分类数据)--分布式锁
    public Map<String, List<Category2Vo>> getCatalogJsonFromDbWithRedisLock() {
        //1.占分布式锁,去redis占坑
        Boolean lock = redisTempalte.opsForValue().setIfAbsent("lock", "111");
        if (lock) {
            //加锁成功。。。执行业务
            Map<String, List<Category2Vo>> dataFromDb = getDataFromDb();
            //执行结束,释放锁(删除锁)
            redisTempalte.delete("lock");
            return dataFromDb;
        } else {
            //加锁失败。。。测试,synchronized()
            //休眠100ms,继续重试
            return getCatalogJsonFromDbWithRedisLock();//自旋的方式
        }
    }

3).分布式锁阶段2

在这里插入图片描述

a.代码

在这里插入图片描述

问题: 设置过期时间后,万一在过期设置过期时间前(如突然断电),出现异常,那就永远死锁了
解决:设置过期时间与占位必须原子

在这里插入图片描述

4).分布式锁阶段3

在这里插入图片描述
设置过期时间与占位必须原子
在这里插入图片描述

    public Map<String, List<Category2Vo>> getCatalogJsonFromDbWithRedisLock() {
        //1.占分布式锁,去redis占坑
        Boolean lock = redisTempalte.opsForValue().setIfAbsent("lock", "111",300,TimeUnit.SECONDS);
        if (lock) {
            //加锁成功。。。执行业务
            //2.设置过期时间,必须与加锁同步,原子的
//            redisTempalte.expire("lock",30,TimeUnit.SECONDS);
            Map<String, List<Category2Vo>> dataFromDb = getDataFromDb();
            //执行结束,释放锁(删除锁)
            redisTempalte.delete("lock");
            return dataFromDb;
        } else {
            //加锁失败。。。测试,synchronized()
            //休眠100ms,继续重试
            return getCatalogJsonFromDbWithRedisLock();//自旋的方式
        }
    }
问题:锁超时时间短,把别人用的锁删除了,咋办

在这里插入图片描述

解决:

在这里插入图片描述

5).分布式锁阶段4

在这里插入图片描述

代码

在这里插入图片描述

  public Map<String, List<Category2Vo>> getCatalogJsonFromDbWithRedisLock() {
        //1.占分布式锁,去redis占坑
        String uuid = UUID.randomUUID().toString();

        Boolean lock = redisTempalte.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
        if (lock) {
            //加锁成功。。。执行业务
            //2.设置过期时间,必须与加锁同步,原子的
//            redisTempalte.expire("lock",30,TimeUnit.SECONDS);
            Map<String, List<Category2Vo>> dataFromDb = getDataFromDb();
            String lockValue = redisTempalte.opsForValue().get("lock");
            if (uuid.equals(lockValue)){
                //执行结束,释放锁(删除自己的锁)
                redisTempalte.delete("lock");
            }
            return dataFromDb;
        } else {
            //加锁失败。。。测试,synchronized()
            //休眠100ms,继续重试
            return getCatalogJsonFromDbWithRedisLock();//自旋的方式
        }
    }
问题:

在这里插入图片描述

解决:

在这里插入图片描述

6).分布式锁阶段5

在这里插入图片描述
在这里插入图片描述

操作:

在这里插入图片描述

代码:

在这里插入图片描述

   //2.获取二级、三机分类(从数据库查询并封装分类数据)--分布式锁
    public Map<String, List<Category2Vo>> getCatalogJsonFromDbWithRedisLock() {
        //1.占分布式锁,去redis占坑
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTempalte.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
        if (lock) {
            System.out.println("获取分布式锁成功。。。");
            //加锁成功。。。执行业务
            //2.设置过期时间,必须与加锁同步,原子的
//            redisTempalte.expire("lock",30,TimeUnit.SECONDS);
            //3.获取值对比+对比成功=原子操作, lua脚本解锁
            Map<String, List<Category2Vo>> dataFromDb=null;
            try {
                dataFromDb = getDataFromDb();
            }catch(Exception e){
                String script=  "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0  end";
                //删除锁
                Integer lock1 = redisTempalte.execute(new DefaultRedisScript<Integer>(script, Integer.class), Arrays.asList("lock"), uuid);
            }
            return dataFromDb;
            //获取值对比+对比成功=原子操作, lua脚本解锁
//            String lockValue = redisTempalte.opsForValue().get("lock");
//            if (uuid.equals(lockValue)){
//                //执行结束,释放锁(删除自己的锁)
//                redisTempalte.delete("lock");
//            }
        } else {
            //加锁失败。。。测试,synchronized()
            //休眠100ms,继续重试
            System.out.println("获取分布式锁失败。。。等待重试");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getCatalogJsonFromDbWithRedisLock();//自旋的方式
        }
    }
问题:锁自动续期(麻烦)
解决:把过期时间设置大点,(其他)

在这里插入图片描述

压力测试–分布式

在这里插入图片描述
在这里插入图片描述

分布式锁总结

1).过期数据+redis占位(加锁)==原子性
2).删除锁获取值对比+对比成功删除=原子操作, lua脚本解锁
3).可以抽取这两部

在这里插入图片描述

进一步学习:分布式锁 框架Redisson

分布式锁框架Redisson

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值