快捷搜索:  汽车  科技

利用redis怎么实现分布式锁(图解Redis分布式锁)

利用redis怎么实现分布式锁(图解Redis分布式锁)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);

分布式锁的演进

基本原理

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

阶段一

利用redis怎么实现分布式锁(图解Redis分布式锁)(1)

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占好了位,业务代码异常或者程序在页面过程中宕机。没有执行删除锁逻辑,这就造成了死锁

解决: 设置锁的自动过期,即使没有删除,会自动删除。最新分布式面试题整理好了,点击Java面试库小程序在线刷题。

阶段二

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,每个人匹配是自己的锁才删除。最新分布式面试题整理好了,点击Java面试库小程序在线刷题。

阶段四

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脚本完成。最新分布式面试题整理好了,点击Java面试库小程序在线刷题。

阶段五-最终形态

利用redis怎么实现分布式锁(图解Redis分布式锁)(2)

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/redisson/wiki

来源:https://blog.csdn.net/zhangkaixuan456/article/details/110679617

猜您喜欢: