分布式锁的演进
基本原理
咱们能够同时去一个中央“占坑”,如果占到,就执行逻辑。否则就必须期待,直到开释锁。“占坑”能够去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开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!