关于java:图解-Redis-分布式锁写得太好了

3次阅读

共计 4591 个字符,预计需要花费 12 分钟才能阅读完成。

分布式锁的演进

基本原理

咱们能够同时去一个中央“占坑”,如果占到,就执行逻辑。否则就必须期待,直到开释锁。“占坑”能够去 redis,能够去数据库,能够去任何大家都能拜访的中央。期待能够自旋的形式。

阶段一

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
        // 阶段一
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
        // 获取到锁,执行业务
        if (lock) {Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            // 删除锁,如果在此之前报错或宕机会造成死锁
            stringRedisTemplate.delete("lock");
            return categoriesDb;
        }else {
            // 没获取到锁,期待 100ms 重试
            try {Thread.sleep(100);
            } catch (InterruptedException e) {e.printStackTrace();
            }
            return getCatalogJsonDbWithRedisLock();}
    }
 
public Map<String, List<Catalog2Vo>> getCategoryMap() {ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String catalogJson = ops.get("catalogJson");
        if (StringUtils.isEmpty(catalogJson)) {System.out.println("缓存不命中,筹备查询数据库。。。");
            Map<String, List<Catalog2Vo>> categoriesDb= getCategoriesDb();
            String toJSONString = JSON.toJSONString(categoriesDb);
            ops.set("catalogJson", toJSONString);
            return categoriesDb;
        }
        System.out.println("缓存命中。。。。");
        Map<String, List<Catalog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});
        return listMap;
    }

问题: setnx 占好了位,业务代码异样或者程序在页面过程中宕机。没有执行删除锁逻辑,这就造成了死锁

解决: 设置锁的主动过期,即便没有删除,会主动删除

阶段二

 public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
        if (lock) {
            // 设置过期工夫
            stringRedisTemplate.expire("lock", 30, TimeUnit.SECONDS);
            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            stringRedisTemplate.delete("lock");
            return categoriesDb;
        }else {
            try {Thread.sleep(100);
            } catch (InterruptedException e) {e.printStackTrace();
            }
            return getCatalogJsonDbWithRedisLock();}
    }

问题: setnx 设置好,正要去设置过期工夫,宕机。又死锁了。

解决: 设置过期工夫和占位必须是原子的。redis 反对应用 setnx ex 命令。

举荐一个开源收费的 Spring Boot 最全教程:

https://github.com/javastacks/spring-boot-best-practice

阶段三

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
    // 加锁的同时设置过期工夫,二者是原子性操作
    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111",5, TimeUnit.SECONDS);
    if (lock) {Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
        // 模仿超长的业务执行工夫
        try {Thread.sleep(6000);
        } catch (InterruptedException e) {e.printStackTrace();
        }
        stringRedisTemplate.delete("lock");
        return categoriesDb;
    }else {
        try {Thread.sleep(100);
        } catch (InterruptedException e) {e.printStackTrace();
        }
        return getCatalogJsonDbWithRedisLock();}
}

问题: 删除锁间接删除???如果因为业务工夫很长,锁本人过期了,咱们间接删除,有可能把他人正在持有的锁删除了。

解决: 占锁的时候,值指定为 uuid,每个人匹配是本人的锁才删除。

阶段四

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {String uuid = UUID.randomUUID().toString();
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
      // 为以后锁设置惟一的 uuid,只有当 uuid 雷同时才会进行删除锁的操作
        Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);
        if (lock) {Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            String lockValue = ops.get("lock");
            if (lockValue.equals(uuid)) {
                try {Thread.sleep(6000);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                stringRedisTemplate.delete("lock");
            }
            return categoriesDb;
        }else {
            try {Thread.sleep(100);
            } catch (InterruptedException e) {e.printStackTrace();
            }
            return getCatalogJsonDbWithRedisLock();}
    }

问题: 如果正好判断是以后值,正要删除锁的时候,锁曾经过期,他人曾经设置到了新的值。那么咱们删除的是他人的锁

解决: 删除锁必须保障原子性。应用 redis+Lua 脚本实现

阶段五 - 最终状态

 public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {String uuid = UUID.randomUUID().toString();
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);
        if (lock) {Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            String lockValue = ops.get("lock");
            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();
            }
            return getCatalogJsonDbWithRedisLock();}
    }

保障加锁【占位 + 过期工夫】和删除锁【判断 + 删除】的原子性。更难的事件,锁的主动续期。

Spring Boot 根底就不介绍了,举荐看这个收费教程:

https://github.com/javastacks/spring-boot-best-practice

Redisson

Redisson 是一个在 Redis 的根底上实现的 Java 驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的 Java 罕用对象,还提供了许多分布式服务。

其中包含 (BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)

Redisson 提供了应用 Redis 的最简略和最便捷的办法。Redisson 的主旨是促成使用者对 Redis 的关注拆散(Separation of Concern),从而让使用者可能将精力更集中地放在解决业务逻辑上。

更多请参考官网文档:

https://github.com/redisson/r…

起源:https://blog.csdn.net/zhangka…

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿 (2022 最新版)

2. 劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0