redis分布式锁演进实例

158集 redis中文官方文档命令

阶段一

在这里插入图片描述

阶段二

在这里插入图片描述

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

        //1.redis占坑  setIfAbsent()就相当于 setnx命令  只有当这个键不存在时才能存储成功
        //为锁设置自动过期时间  避免业务出错或断电导致删锁语句未执行  一直死锁
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111",300,TimeUnit.SECONDS);
        if(lock){
            //占锁成功
            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();//这一步执行业务代码可能出错 抛异常直接退出
            //了  这样就不会执行下面这句删锁代码
            //也可能刚好执行完上面这句 没有异常  正要执行下面这句删锁代码时程序断电了  这样也不会执行下面这句删锁代码
            stringRedisTemplate.delete("lock");//抢占锁获取数据完成后要删除锁
            return dataFromDb;
        }else{
            //占锁失败  重试  通过自旋的方式  过一会儿继续尝试占锁  只有占到锁才能查询数据库
            return  getCatelogJSONFromDbWithRedisLock();
        }
    }

阶段三

在这里插入图片描述

阶段四

在这里插入图片描述

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

        //1.redis占坑  setIfAbsent()就相当于 setnx命令  只有当这个键不存在时才能存储成功
        //为锁设置自动过期时间  避免业务出错或断电导致删锁语句未执行  一直死锁
        String uuid = UUID.randomUUID().toString();

        //占锁时 不同的实例产生的uuid可能会一样 这就冲突了 可以设置为时间戳加uuid

        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
        if(lock){
            //占锁成功
            Map<String, List<Catelog2Vo>> dataFromDb;
            try{
               dataFromDb = getDataFromDb();//这一步执行业务代码可能出错 抛异常直接退出
            }finally {
                //  使用 Lua 脚本来 删除 锁  (解锁)    ctrl+h  查看这个接口的实现类
                //使用脚本删锁 可以保证 对比值+删锁 这两步操作 是原子的
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return  redis.call('del', KEYS[1]) else return 0 end";
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class),//Long 返回的数据类型
                        Arrays.asList("lock"),uuid);
            }

            //了  这样就不会执行下面这句删锁代码
            //也可能刚好执行完上面这句 没有异常  正要执行下面这句删锁代码时程序断电了  这样也不会执行下面这句删锁代码

//            String lock1 = stringRedisTemplate.opsForValue().get("lock");
//            if(uuid.equals(lock1)){
//                stringRedisTemplate.delete("lock");// 删除自己的锁 抢占锁获取数据完成后要删除锁
//            }

            return dataFromDb;
        }else{
            //占锁失败  重试  通过自旋的方式  过一会儿继续尝试占锁  只有占到锁才能查询数据库
            try{
                Thread.sleep(200); //这里如果不睡  直接递归调用  可能会栈空间溢出
            }catch (Exception e){
                e.printStackTrace();
            }

            return  getCatelogJSONFromDbWithRedisLock();
        }
    }

测试

复制多个服务 使用jmeter测试 多个不同服务竞争分布式锁 得到锁的线程再去查数据库 查完放缓存 之前的本地锁 几个服务中 每个服务都查询了一次数据库 只能锁住本地服务的线程 使用分布式锁 几个服务一共应该只查询一次数据库
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
jmeter测试
在这里插入图片描述
之前使用jmeter进行测试 出现很多异常在这里插入图片描述
直接访问testmall.com/index/catalog.json也访问不到 访问本地http://localhost:11000/index/catalog,json能访问到
nginx的配置文件也正常 但就是访问不到这个域名 使用SwitchHost软件查看当前windows的域名映射 点击应用后 再访问就可以
此时 jmeter中500个请求全部正常执行 没有报错
在这里插入图片描述
在idea中查看 真正执行数据库查询的方法输出的"在锁中缓存未命中。。。。" 只在一个服务中出现了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

说明锁住了三个服务 三个服务中的线程去竞争锁 只有一个线程得到锁 执行查询后 后面的线程都从缓存中获取数据

总结:

  • redis分布式锁加锁操作需要是原子的 set NX EX (NX不存在时才保存值 EX设置锁的自动过期时间);
  • 解锁操作也需要是 原子 的 ,对比值和删锁;
  • 锁的自动续期 就是说锁的自动过期时间到了 但还没执行到删除锁那一步 如果不续期 锁过期了 其他线程拿到锁 在执行删锁 可能就会删掉别的线程的锁 所以当自动过期时间到了 还没执行删锁操作 需要对锁进行自动续期 最简单的方式就是把自动过期时间设置长一点
  • redis自己实现分布式锁 需要自旋 递归调用尝试占锁的方法 可以使用更专业的框架来完成分布式锁Redisson

Redisson使用示例

引入依赖 配置(程序配置或文件配置)

   @GetMapping("/hello")
    public String testRedissonClient(){
        //根据名字获取锁  只要锁名一样  就是同一把锁   第一个线程拿到这个锁 加锁 第二个线程拿到的就是 一个正在运行的加了锁的锁
        RLock mylock = redissonClient.getLock("my-lock");

        //加锁   redisson 加的锁会自动给锁设置一个30秒的过期时间TTL  避免没有执行解锁代码造成的死锁
        mylock.lock();//这个方法是一个阻塞式等待方法  如果拿不到锁一直在这里等待
        try{
         //1.如果业务超长  redisson会自动给锁续期  续成还有30秒过期  看门狗机制  避免业务还没执行完锁就自动过期
              //2.加锁的业务只要运行完成,就不会给当前锁续期 即使不手动解锁  锁也会在30秒后过期
            System.out.println("加锁成功,执行业务代码。。。。"+Thread.currentThread().getId());
            Thread.sleep(30000);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("释放锁。。。。"+Thread.currentThread().getId());
            mylock.unlock();
        }

        return "hello";
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值