分布式锁原理及使用

一、本地锁

 锁-时序问题:

二、分布式锁演进

1、基本原理

我们可以同时去一个地方“占坑”,如果占到,就执行逻辑。否则就必须等待,直到释放锁。占坑可以去Redis,也可以去数据库,可以去任何大家都可以访问到的地方。等待可以自旋的方式。

2、阶段一

 

 Redis官网NX参数说明

NX – 只有键key不存在的时候才会设置key的值。

解读:每个线程在访问时,先会去判断这个“坑位”有没有加锁,如果没有加锁,则自己占住加锁(然后获取数据,从缓存/数据库),别人来的时候看到加锁就会等待,当从坑位离开时,会删除锁,然后其他的才会继续站位。

进入redis:   

> docker exec -it redis redis-cli

# 可以多开几个窗口进行测试
127.0.0.1:6379> set lock 1 NX
OK

阶段一示例代码:

String uuid = UUID.randomUUID().toString();
    ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
    Boolean lock = ops.setIfAbsent("lock", uuid,500, TimeUnit.SECONDS);
    if (lock) {
        //加锁成功执行业务
        Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
        stringRedisTemplate.delete("lock");//删除锁
        return categoriesDb;
    }else {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 睡眠0.1s后,重新调用 //自旋
        return getCatalogJsonDbWithRedisLock();
    }

3、阶段二

 阶段二示例代码:

Boolean lock = ops.setIfAbsent("lock", uuid,500, TimeUnit.SECONDS);//设置过期时间和占位原子性

4、阶段三

阶段三示例代码:

String uuid = UUID.randomUUID().toString();
    ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
    Boolean lock = ops.setIfAbsent("lock", uuid,500, TimeUnit.SECONDS);
    if (lock) {
        Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
      	//删除锁
        //先去redis查询下保证当前的锁是自己的
        //获取值对比,对比成功删除= 丢失原子性--->需要解决
        String lockValue = stringRedisTemplate.opsForValue().get("lock");
        if (uuid.equals(lockValue)) {
          //删除我自己的锁
          stringRedisTemplate.delete("lock");
        }
        return categoriesDb;
    }else {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 睡眠0.1s后,重新调用 //自旋
        return getCatalogJsonDbWithRedisLock();
    }

5、阶段四

阶段四示例代码:

	String uuid = UUID.randomUUID().toString();
    ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
    Boolean lock = ops.setIfAbsent("lock", uuid,500, TimeUnit.SECONDS);
    if (lock) {
        Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
        String lockValue = ops.get("lock");
        // get和delete原子操作
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
            "    return redis.call(\"del\",KEYS[1])\n" +
            "else\n" +
            "    return 0\n" +
            "end";
        stringRedisTemplate.execute(
            new DefaultRedisScript<Long>(script, Long.class), // 脚本和返回类型
            Arrays.asList("lock"), // 参数
            lockValue); // 参数值,锁的值
        return categoriesDb;
    }else {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 睡眠0.1s后,重新调用 //自旋
        return getCatalogJsonDbWithRedisLock();
    }

6、最终形态

分布式锁代码

//分布式锁三级分类查询
    @Override
    public HashMap<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock(){
        //先去缓存查
        String catalogListCache = stringRedisTemplate.opsForValue().get("catalogListCache");

        //缓存有 直接返回
        if(catalogListCache!= null){
            HashMap<String, List<Catalog2Vo>> stringListHashMap = JSON.parseObject(catalogListCache, new TypeReference<HashMap<String, List<Catalog2Vo>>>() {
            });
            System.out.println("缓存命中");
            return stringListHashMap;
        }else {
            //1去redis占锁
            String uuid = UUID.randomUUID().toString();
            ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
            //占坑 给时间 原子性操作
            Boolean lock = ops.setIfAbsent("lock",uuid, 30, TimeUnit.SECONDS);
            if (lock) { //获取锁成功
                System.out.println("获取分布式锁成功...");
                try {
                    //执行业务
                    //redis没有去数据库查
                    HashMap<String, List<Catalog2Vo>> dbmap = catalogList();
                    String s = JSON.toJSONString(dbmap);

                    //如果数据库查到也是null
                    if(s == null || StringUtils.isEmpty(s)){
                        //缓存穿透 短暂缓存null值
                        stringRedisTemplate.opsForValue().set("catalogListCache",s,30, TimeUnit.SECONDS);
                    }
                    //加入缓存时间  随机值 解决雪崩问题
                    Random random = new Random();
                    int i = random.nextInt(300);
                    //查完放入缓存
                    stringRedisTemplate.opsForValue().set("catalogListCache",s,300+i, TimeUnit.SECONDS);
                    return dbmap;
                }finally {
                    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), Arrays.asList("lock"), uuid);
                }

            }else{ //没有获取锁 自旋等待
                System.out.println("获取分布式锁失败...等待重试...");
                //加锁失败...重试机制
                //休眠一百毫秒
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return getCatalogJsonFromDbWithRedisLock();
            }
        }

    }

猜你喜欢

转载自blog.csdn.net/weixin_68829137/article/details/127142989