当前位置: 首页 > 后端技术 > Java

图解Redis分布式锁,写的好!

时间:2023-04-02 02:05:39 Java

分布式锁进化的基本原理我们可以同时去一个地方“占一个洞”,如果被占了,就执行逻辑。否则,您必须等到锁被释放。“站坑”可以到redis,到数据库,到任何一个人人都能访问到的地方。等待可以这样旋转。Phase1publicMap>getCatalogJsonDbWithRedisLock(){//Phase1Booleanlock=stringRedisTemplate.opsForValue().setIfAbsent("lock","111");//获取锁并执行业务if(lock){Map>categoriesDb=getCategoryMap();//删除锁,如果在此之前发生错误或者宕机会导致死锁stringRedisTemplate.delete("lock");返回类别Db;}else{//没有拿到锁,等100ms再试try{Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}返回getCatalogJsonDbWithRedisLock();}}publicMap>getCategoryMap(){ValueOperationsops=stringRedisTemplate.opsForValue();StringcatalogJson=ops.get("catalogJson");如果(StringUtils.isEmpty(目录Json)){System.out.println("缓存未命中,准备查询数据库...");Map>categoriesDb=getCategoriesDb();字符串toJSONString=JSON.toJSONString(categoriesDb);操作。设置(“catalogJson”,toJSONString);返回类别Db;}System.out.println("缓存命中...");Map>listMap=JSON.parseObject(catalogJson,newTypeReference>>(){});返回列表映射;}问题: setnx占位,页面处理过程中业务代码异常或程序崩溃。删除锁的逻辑没有执行,导致死锁解决:设置锁自动过期,即使不删除也会自动删除第二阶段publicMap>getCatalogJsonDbWithRedisLock(){Booleanlock=stringRedisTemplate.opsForValue().setIfAbsent("lock","111");if(lock){//设置过期时间stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);Map>categoriesDb=getCategoryMap();stringRedisTemplate.delete("锁定");返回类别Db;}else{尝试{Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}返回getCatalogJsonDbWithRedisLock();setnx设置好了,我要设置过期时间了,机器宕机了。再次陷入僵局。解决方案:设置过期时间和占位符必须是原子的。Redis支持使用setnxex命令。推荐一个开源免费的SpringBoot最全教程:https://github.com/javastacks/spring-boot-best-practicePhase3publicMap>getCatalogJsonDbWithRedisLock(){//同时加锁Set过期时间,两者都是原子操作booleanlock=stringRedisTemplate.opsForValue().setIfAbsent("lock","1111",5,TimeUnit.SECONDS);如果(锁){Map>categoriesDb=getCategoryMap();//模拟一个较长的业务执行时间try{Thread.sleep(6000);}catch(InterruptedExceptione){e.printStackTrace();}stringRedisTemplate.delete("锁定");返回类别Db;}else{尝试{Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}返回getCatalogJsonDbWithRedisLock();}}问题: 直接删除锁???如果锁本身因为业务时间长而过期,我们直接删除,有可能删除别人持有的锁。解决方法:占用锁时,指定值为uuid,每个人匹配自己的锁后才删除。阶段4publicMap>getCatalogJsonDbWithRedisLock(){Stringuuid=UUID.randomUUID().toString();ValueOperationsops=stringRedisTemplate.opsForValue();//为当前锁设置唯一的uuid,只有uuid相同才会删除锁操作booleanlock=ops.setIfAbsent("lock",uuid,5,TimeUnit.SECONDS);如果(锁){Map>categoriesDb=getCategoryMap();StringlockValue=ops.get("lock");如果(lockValue.equals(uuid)){尝试{Thread.sleep(6000);}catch(InterruptedExceptione){e.printStackTrace();}stringRedisTemplate.delete("锁定");}返回类别数据库;}else{尝试{Thread.sleep(100);}catch(InterruptedExceptione){e.printSt确认跟踪();}返回getCatalogJsonDbWithRedisLock();}}问题:如果恰好是当前值,要删除锁,锁已经过期,别人设置了新的值,那么我们删除的是别人的锁解决方法:删除锁必须保证原子性。使用redis+Lua脚本完成阶段五-最终形态publicMap>getCatalogJsonDbWithRedisLock(){Stringuuid=UUID.randomUUID().toString();ValueOperationsops=stringRedisTemplate.opsForValue();Booleanlock=ops.setIfAbsent("lock",uuid,5,TimeUnit.SECONDS);如果(锁){Map>categoriesDb=getCategoryMap();StringlockValue=ops.get("lock");Stringscript="ifredis.call(\"get\",KEYS[1])==ARGV[1]then\n"+"returnredis.call(\"del\",KEYS[1])\n"+"else\n"+"返回0\n"+"结束";stringRedisTemplate.execute(newDefaultRedisScript(script,Long.class),Arrays.asList("lock"),lockValue);返回类别数据库;}else{尝试{Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}返回getCatalogJsonDbWithRedisLock();+Delete]比较难的事情的原子性,锁的自动更新。我不会介绍SpringBoot的基础知识。推荐观看这个免费教程:https://github.com/javastacks/spring-boot-best-practiceRedissonRedisson是在RedisDataGrid的基础上实现的Java内存中数据网格(In-Memory)。它不仅提供了一系列分布式通用Java对象,还提供了很多分布式服务。这些包括(BitSet,Set,Multimap,SortedSet,Map,List,Queue,BlockingQueue,Deque,BlockingDeque,Semaphore,Lock,AtomicLong,CountDownLatch,Publish/Subscribe,Bloomfilter,Remoteservice,Springcache,Executorservice,LiveObjectservice,Schedulerservice)Redisson提供了最简单、最方便的Redis使用方式。Redisson的目的是促进用户对Redis的关注点分离(SeparationofConcern),让用户更专注于处理业务逻辑。更多内容请参考官方文档:https://github.com/redisson/r...来源:https://blog.csdn.net/zhangka...推荐近期文章:Java1.1,000+面试题及答案安排(2022最新版)2.世界之最!Java协程来了。..3.SpringBoot2.x教程,太全面了!4.不要用爆破爆满画面,试试装饰者模式,这才是优雅的方式!!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!