2021年欧冠直播间

admin · 2018-02-01

  

  新接办的名目,临时会崭露账不服的成绩。以前的本领垂老临走时给的注解是:排查了,没找到出处,以后太忙就没再管理,大概是框架的出处……

  既然名目交付得手中,如许的成绩是必要要管理的。梳理了全盘账务处罚逻辑,终极找到了出处:数据库并发操纵热门账户招致。就这这个成绩,来聊一聊散布式体系下基于Redis的散布式锁。趁便也明白一下成绩造成出处及管理计划。

   出处阐发

  体系并发量并不高,存正在热门账户,但也不至于那末厉峻。成绩的泉源正在于体系架构计划,人工的制作了并发。场景是如许的:商户批量导入一批数据,体系会举行前置处罚,并对账户余额举行增减。

  此时,另一个按时工作,也会对账户举行扫描更新。并且对统一账户的操纵散布到各个人系傍边,热门账户也就崭露了。

  针对此成绩的管理计划,从架构层面能够斟酌将账务体系举行抽离,鸠合正在一个人系中举行处罚,全盘的数据库事件及奉行序次由账务体系来兼顾处罚。从本领方面来说,则能够经由过程锁机制来对热门账户举行加锁。

  本篇作品就针对热门账户基于散布式锁的竣工格式举行详尽的讲明。

   锁的阐发

  正在Java的众线程境遇下,寻常有几类锁能够运用:

   JVM内存模子级其余锁,常用的有:synchronized、Lock等; 数据库锁,比方悲观锁,消沉锁等; 散布式锁;

  JVM内存级其余锁,能够担保单体任职下线程的安宁性,比方众个线程接见/窜改一个全体变量。但当体系举行集群安置时,JVM级其余当地锁就无计可施了。

   消沉锁与悲观锁

  像上述案例中,热门账户就属于散布式体系中的同享资本,咱们寻常会采取数据库锁或散布式锁来举行管理。

  数据库锁,又分为悲观锁和消沉锁。

  消沉锁是基于数据库(Mysql的InnoDB)供应的排他锁来竣工的。正在举行事件操纵时,经由过程select ... for update语句,MySQL会对查问了局鸠合每行数据都增添排他锁,其余线程对该记载的更新与删除操纵都邑窒碍。从而到达同享资本的序次奉行(窜改);

  悲观锁是绝对消沉锁而言的,悲观锁假定数据大凡状况不会形成抵触,以是正在数据举行提交更新的光阴,才会正式对数据的抵触与否举行检测。倘若抵触则前往给用户特殊音信,让用户决意怎样去做。悲观锁合用于读众写少的场景,如许能够抬高步伐的含糊量。正在悲观锁竣工时寻常会基于记载状况或增添version版从来举行竣工。

   消沉锁生效场景

  名目中运用了消沉锁,但消沉锁却生效了。这也是运用消沉锁时,常睹的误区,上面来阐发一下。

  寻常运用消沉锁的流程:

   经由过程select ... for update锁定记载; 估量新余额,窜改金额并存储; 奉行完毕开释锁;

  每每出错的处罚流程:

   查问账户余额,估量新余额; 经由过程select ... for update锁定记载; 窜改金额并存储; 奉行完毕开释锁;

  失误的流程中,比方A和B任职查问到的余额都是100,A扣减50,B扣减40,而后A锁定记载,更新数据库为50;A开释锁以后,B锁定记载,更新数据库为60。显明,后者把前者的更新给掩盖掉了。管理的计划即是扩展锁的规模,将锁提前到估量新余额以前。

  寻常消沉锁对数据库的压力黑白常大的,正在实习中寻常会依照场景运用悲观锁或散布式锁等格式来竣工。

  上面进入正题,讲讲基于Redis的散布式锁竣工。

   Redis散布式锁实战练习

  这里以Spring Boot、Redis、Lua剧本为例来演示散布式锁的竣工。为了简化处罚,示例中Redis既负责了散布式锁的效力,也负责了数据库的效力。

   场景构修

  集群境遇下,对统一个账户的金额举行操纵,根本步调:

   从数据库读取用户金额; 步伐窜改金额; 再将最新金额存储到数据库; 上面从最初不加锁,差别步处罚,渐渐推上演终极的散布式锁。 基本集成及类构修

  打算一个不加锁处罚的基本交易境遇。

  起首正在Spring Boot名目中引入闭连依附:

  

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

 

  账户对应实体类UserAccount:

  

publicclassUserAccount{//用户IDprivateStringuserId;//账户内金额privateintamount;//增添账户金额publicvoidaddAmount(intamount){this.amount=this.amount+amount;}//省略构制法子和getter/setter}

 

  创修一个线程竣工类AccountOperationThread:

  

publicclassAccountOperationThreadimplementsRunnable{privatefinalstaticLoggerlogger=LoggerFactory.getLogger(AccountOperationThread.class);privatestaticfinalLongRELEASE_SUCCESS=1L;privateStringuserId;privateRedisTemplate<Object,Object>redisTemplate;publicAccountOperationThread(StringuserId,RedisTemplate<Object,Object>redisTemplate){this.userId=userId;this.redisTemplate=redisTemplate;}@Overridepublicvoidrun(){noLock();}/***不加锁*/privatevoidnoLock(){try{Randomrandom=newRandom();//摹拟线程举行交易处罚TimeUnit.MILLISECONDS.sleep(random.nextInt(100)+1);}catch(InterruptedExceptione){e.printStackTrace();}//摹拟数据库中获取用户账号UserAccountuserAccount=(UserAccount)redisTemplate.opsForValue().get(userId);//金额+1userAccount.addAmount(1);logger.info(Thread.currentThread().getName()+":userid:"+userId+"amount:"+userAccount.getAmount());//摹拟存回数据库redisTemplate.opsForValue().set(userId,userAccount);}}

 

  个中RedisTemplate的实例化交给了Spring Boot:

  

@ConfigurationpublicclassRedisConfig{@BeanpublicRedisTemplate<Object,Object>redisTemplate(RedisConnectionFactoryredisConnectionFactory){RedisTemplate<Object,Object>redisTemplate=newRedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);Jackson2JsonRedisSerializer<Object>jackson2JsonRedisSerializer=newJackson2JsonRedisSerializer<>(Object.class);ObjectMapperobjectMapper=newObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);//配置value的序列化轨则和key的序列化轨则redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setKeySerializer(newStringRedisSerializer());redisTemplate.afterPropertiesSet();returnredisTemplate;}}

 

  终末,再打算一个TestController来举行触发众线程的运转:

  

@RestControllerpublicclassTestController{privatefinalstaticLoggerlogger=LoggerFactory.getLogger(TestController.class);privatestaticExecutorServiceexecutorService=Executors.newFixedThreadPool(10);@AutowiredprivateRedisTemplate<Object,Object>redisTemplate;@GetMapping("/test")publicStringtest()throwsInterruptedException{//初始化用户user_001到Redis,账户金额为0redisTemplate.opsForValue().set("user_001",newUserAccount("user_001",0));//开启10个线程举行同步测试,每一个线程为账户推广1元for(inti=0;i<10;i++){logger.info("创修线程i="+i);executorService.execute(newAccountOperationThread("user_001",redisTemplate));}//主线程歇眠1秒等候线程跑完TimeUnit.MILLISECONDS.sleep(1000);//查问Redis中的user_001账户UserAccountuserAccount=(UserAccount)redisTemplate.opsForValue().get("user_001");logger.info("userid:"+userAccount.getUserId()+"amount:"+userAccount.getAmount());return"success";}}

 

  奉行上述步伐,寻常来讲10个线程,每一个线程加1,了局该当是10。但众奉行几回,会挖掘,了局改变很大,根本上都要比10小。

  

[pool-1-thread-5]c.s.redis.thread.AccountOperationThread:pool-1-thread-5:userid:user_001amount:1[pool-1-thread-4]c.s.redis.thread.AccountOperationThread:pool-1-thread-4:userid:user_001amount:1[pool-1-thread-3]c.s.redis.thread.AccountOperationThread:pool-1-thread-3:userid:user_001amount:1[pool-1-thread-1]c.s.redis.thread.AccountOperationThread:pool-1-thread-1:userid:user_001amount:1[pool-1-thread-1]c.s.redis.thread.AccountOperationThread:pool-1-thread-1:userid:user_001amount:2[pool-1-thread-2]c.s.redis.thread.AccountOperationThread:pool-1-thread-2:userid:user_001amount:2[pool-1-thread-5]c.s.redis.thread.AccountOperationThread:pool-1-thread-5:userid:user_001amount:2[pool-1-thread-4]c.s.redis.thread.AccountOperationThread:pool-1-thread-4:userid:user_001amount:3[pool-1-thread-1]c.s.redis.thread.AccountOperationThread:pool-1-thread-1:userid:user_001amount:4[pool-1-thread-3]c.s.redis.thread.AccountOperationThread:pool-1-thread-3:userid:user_001amount:5[nio-8080-exec-1]c.s.redis.controller.TestController:userid:user_001amount:5

 

  以上述日记为例,前四个线程都将值改为1,也即是前面三个线程都将后面的窜改举行了掩盖,招致终极了局不是10,只要5。这显明是有成绩的。

   Redis同步锁竣工

  针对下面的状况,正在统一个JVM傍边,咱们能够经由过程线程加锁来完毕。但正在散布式境遇下,JVM级其余锁是没方法竣工的,这里能够采取Redis同步锁竣工。

  根本思绪:第一个线程进入时,正在Redis中进记载,当后续线程过去恳求时,判定Redis能否存正在该记载,倘若存正在则评释处于锁定状况,举行等候或前往。倘若不存正在,则举行后续交易处罚。

  

/***1.抢占资本时判定能否被锁。*2.如未锁则抢占胜利且加锁,不然等候锁开释。*3.交易完毕后开释锁,让给别的线程。*<p>*该计划并未管理同步成绩,出处:线程得到锁和加锁的进程,并非原子性操纵,大概会招致线程A得到锁,还未加锁时,线程B也得到了锁。*/privatevoidredisLock(){Randomrandom=newRandom();try{TimeUnit.MILLISECONDS.sleep(random.nextInt(1000)+1);}catch(InterruptedExceptione){e.printStackTrace();}while(true){Objectlock=redisTemplate.opsForValue().get(userId+":syn");if(lock==null){//得到锁->加锁->跳出轮回logger.info(Thread.currentThread().getName()+":得到锁");redisTemplate.opsForValue().set(userId+":syn","lock");break;}try{//等候500毫秒重试得到锁TimeUnit.MILLISECONDS.sleep(500);}catch(InterruptedExceptione){e.printStackTrace();}}try{//摹拟数据库中获取用户账号UserAccountuserAccount=(UserAccount)redisTemplate.opsForValue().get(userId);if(userAccount!=null){//配置金额userAccount.addAmount(1);logger.info(Thread.currentThread().getName()+":userid:"+userId+"amount:"+userAccount.getAmount());//摹拟存回数据库redisTemplate.opsForValue().set(userId,userAccount);}}finally{//开释锁redisTemplate.delete(userId+":syn");logger.info(Thread.currentThread().getName()+":开释锁");}}

 

  正在while代码块中,先判定对操纵户ID能否正在Redis中存正在,倘若不存正在,则举行set加锁,倘若存正在,则跳出轮回接续等候。

  上述代码,看起来竣工了加锁的效力,但当奉行步伐时,会挖掘与未加锁同样,仿照存正在并发成绩。出处是:获取锁和加锁的操纵并非原子的。比方两个线程挖掘lock都是null,都举行了加锁,此时并发成绩仿照存正在。

   Redis原子性同步锁

  针对上述成绩,可将获取锁和加锁的进程原子化处罚。基于spring-boot-data-redis供应的原子化API能够竣工:

  

//该法子运用了redis的指令:SETNXkeyvalue//1.key不存正在,配置胜利前往value,setIfAbsent前往true;//2.key存正在,则配置打击前往null,setIfAbsent前往false;//3.原子性操纵;BooleansetIfAbsent(Kvar1,Vvar2);

 

  上述法子的原子化操纵是对Redis的setnx号召的封装,正在Redis中setnx的运用如下实例:

  

redis>SETNXmykey"Hello"(integer)1redis>SETNXmykey"World"(integer)0redis>GETmykey"Hello"

 

  第一次,配置mykey时,并不存正在,则前往1,展现配置胜利;第二次配置mykey时,仍旧存正在,则前往0,展现配置打击。再次查问mykey对应的值,会挖掘仿照是第一次配置的值。也即是说redis的setnx担保了独一的key只可被一个任职配置胜利。

  领略了上述API及底层道理,来看看线程中的竣工法子代码如下:

  

/***1.原子操纵加锁*2.角逐线程轮回重试得到锁*3.交易完毕开释锁*/privatevoidatomicityRedisLock(){//Springdataredis助助的原子性操纵while(!redisTemplate.opsForValue().setIfAbsent(userId+":syn","lock")){try{//等候100毫秒重试得到锁TimeUnit.MILLISECONDS.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}}logger.info(Thread.currentThread().getName()+":得到锁");try{//摹拟数据库中获取用户账号UserAccountuserAccount=(UserAccount)redisTemplate.opsForValue().get(userId);if(userAccount!=null){//配置金额userAccount.addAmount(1);logger.info(Thread.currentThread().getName()+":userid:"+userId+"amount:"+userAccount.getAmount());//摹拟存回数据库redisTemplate.opsForValue().set(userId,userAccount);}}finally{//开释锁redisTemplate.delete(userId+":syn");logger.info(Thread.currentThread().getName()+":开释锁");}}

 

  再次奉行代码,会挖掘了局精确了,也即是说能够胜利的对散布式线程举行了加锁。

   Redis散布式锁的死锁

  固然上述代码奉行了局没成绩,但倘若操纵特殊宕机,没来得及奉行finally中开释锁的法子,那末其余线程则长远无奈得到这个锁。

  此时可采取setIfAbsent的重载法子:

  

BooleansetIfAbsent(Kvar1,Vvar2,longvar3,TimeUnitvar5);

 

  基于该法子,能够配置锁的逾期韶华。如许即使得到锁的线程宕机,正在Redis中数据逾期以后,其余线程可寻常得到该锁。

  示例代码如下:

  

privatevoidatomicityAndExRedisLock(){try{//Springdataredis助助的原子性操纵,并配置5秒逾期韶华while(!redisTemplate.opsForValue().setIfAbsent(userId+":syn",System.currentTimeMillis()+5000,5000,TimeUnit.MILLISECONDS)){//等候100毫秒重试得到锁logger.info(Thread.currentThread().getName()+":实验轮回获取锁");TimeUnit.MILLISECONDS.sleep(1000);}logger.info(Thread.currentThread().getName()+":得到锁--------");//操纵正在这里宕机,过程退出,无奈奉行finally;Thread.currentThread().interrupt();//交易逻辑...}catch(InterruptedExceptione){e.printStackTrace();}finally{//开释锁if(!Thread.currentThread().isInterrupted()){redisTemplate.delete(userId+":syn");logger.info(Thread.currentThread().getName()+":开释锁");}}}

交易超时及保卫线程

 

  下面增添了Redis所的超经常间,看似管理了成绩,但又引入了新的成绩。

  比方,寻常状况下线程A正在5秒内可寻常处罚完交易,但偶发会崭露领先5秒的状况。倘若将超经常间配置为5秒,线程A得到了锁,但交易逻辑处罚须要6秒。此时,线程A还正在寻常交易逻辑,线程B仍旧得到了锁。当线程A处罚完时,有大概将线程B的锁给开释掉。

  正在上述场景中有两个成绩点:

   第一,线程A和线程B大概会同时正在奉行,存正在并发成绩。 第二,线程A大概会把线程B的锁给开释掉,招致一系列的恶性轮回。

  固然,能够经由过程正在Redis中配置value值来判定锁是属于线程A仍是线程B。但认真阐发会挖掘,这个成绩的实质是由于线程A奉行交易逻辑耗时超越了锁超时的韶华。

  那末就有两个管理计划了:

   第一,将超经常间配置的充足长,确保交易代码可能正在锁开释以前奉行完毕; 第二,为锁增添保卫线程,为将要逾期开释但未开释的锁推广韶华;

  第一种格式须要全行大大都状况下交易逻辑的耗时,举行超经常间的设定。

  第二种格式,可经由过程如下保卫线程的格式来静态推广锁超经常间。

  

publicclassDaemonThreadimplementsRunnable{privatefinalstaticLoggerlogger=LoggerFactory.getLogger(DaemonThread.class);//能否须要保卫主线程合上则停止保卫线程privatevolatilebooleandaemon=true;//保卫锁privateStringlockKey;privateRedisTemplate<Object,Object>redisTemplate;publicDaemonThread(StringlockKey,RedisTemplate<Object,Object>redisTemplate){this.lockKey=lockKey;this.redisTemplate=redisTemplate;}@Overridepublicvoidrun(){try{while(daemon){longtime=redisTemplate.getExpire(lockKey,TimeUnit.MILLISECONDS);//赢余无效期小于1秒则续命if(time<1000){logger.info("保卫过程:"+Thread.currentThread().getName()+"延迟锁韶华5000毫秒");redisTemplate.expire(lockKey,5000,TimeUnit.MILLISECONDS);}TimeUnit.MILLISECONDS.sleep(300);}logger.info("保卫过程:"+Thread.currentThread().getName()+"合上");}catch(InterruptedExceptione){e.printStackTrace();}}//主线程自动挪用停止publicvoidstop(){daemon=false;}}

 

  上述线程每隔300毫秒获取一下Redis中锁的超经常间,倘若小于1秒,则延迟5秒。当主线程挪用合上时,保卫线程也随之合上。

  主线程中闭连代码竣工:

  

privatevoiddeamonRedisLock(){//保卫线程DaemonThreaddaemonThread=null;//Springdataredis助助的原子性操纵,并配置5秒逾期韶华Stringuuid=UUID.randomUUID().toString();Stringvalue=Thread.currentThread().getId()+":"+uuid;try{while(!redisTemplate.opsForValue().setIfAbsent(userId+":syn",value,5000,TimeUnit.MILLISECONDS)){//等候100毫秒重试得到锁logger.info(Thread.currentThread().getName()+":实验轮回获取锁");TimeUnit.MILLISECONDS.sleep(1000);}logger.info(Thread.currentThread().getName()+":得到锁----");//开启保卫线程daemonThread=newDaemonThread(userId+":syn",redisTemplate);Threadthread=newThread(daemonThread);thread.start();//交易逻辑奉行10秒...TimeUnit.MILLISECONDS.sleep(10000);}catch(InterruptedExceptione){e.printStackTrace();}finally{//开释锁这里也须要原子操纵,以来经由过程Redis+Lua讲Stringresult=(String)redisTemplate.opsForValue().get(userId+":syn");if(value.equals(result)){redisTemplate.delete(userId+":syn");logger.info(Thread.currentThread().getName()+":开释锁-----");}//合上保卫线程if(daemonThread!=null){daemonThread.stop();}}}

 

  个中正在得到锁以后,开启保卫线程,正在finally中将保卫线程合上。

   基于Lua剧本的竣工

  正在上述逻辑中,咱们是基于spring-boot-data-redis供应的原子化操纵来担保锁判定和奉行的原子化的。正在非Spring Boot名目中,则能够基于Lua剧本来竣工。

  起首界说加锁妥协锁的Lua剧本及对应的DefaultRedisScript工具,正在RedisConfig装备类中增添如下实例化代码:

  

@ConfigurationpublicclassRedisConfig{//lockscriptprivatestaticfinalStringLOCK_SCRIPT="ifredis.call(setnx,KEYS[1],ARGV[1])==1"+"thenredis.call(expire,KEYS[1],ARGV[2])"+"return1"+"elsereturn0end";privatestaticfinalStringUNLOCK_SCRIPT="ifredis.call(get,KEYS[1])==ARGV[1]thenreturnredis.call"+"(del,KEYS[1])elsereturn0end";//...省略片面代码@BeanpublicDefaultRedisScript<Boolean>lockRedisScript(){DefaultRedisScript<Boolean>defaultRedisScript=newDefaultRedisScript<>();defaultRedisScript.setResultType(Boolean.class);defaultRedisScript.setScriptText(LOCK_SCRIPT);returndefaultRedisScript;}@BeanpublicDefaultRedisScript<Long>unlockRedisScript(){DefaultRedisScript<Long>defaultRedisScript=newDefaultRedisScript<>();defaultRedisScript.setResultType(Long.class);defaultRedisScript.setScriptText(UNLOCK_SCRIPT);returndefaultRedisScript;}}

 

  再经由过程正在AccountOperationThread类中新修构制法子,将上述两个工具传入类中(省略此片面演示)。而后,就能够基于RedisTemplate来挪用了,改制以后的代码竣工如下:

  

privatevoiddeamonRedisLockWithLua(){//保卫线程DaemonThreaddaemonThread=null;//Springdataredis助助的原子性操纵,并配置5秒逾期韶华Stringuuid=UUID.randomUUID().toString();Stringvalue=Thread.currentThread().getId()+":"+uuid;try{while(!redisTemplate.execute(lockRedisScript,Collections.singletonList(userId+":syn"),value,5)){//等候1000毫秒重试得到锁logger.info(Thread.currentThread().getName()+":实验轮回获取锁");TimeUnit.MILLISECONDS.sleep(1000);}logger.info(Thread.currentThread().getName()+":得到锁----");//开启保卫线程daemonThread=newDaemonThread(userId+":syn",redisTemplate);Threadthread=newThread(daemonThread);thread.start();//交易逻辑奉行10秒...TimeUnit.MILLISECONDS.sleep(10000);}catch(InterruptedExceptione){logger.error("特殊",e);}finally{//运用Lua剧本:先判定是不是本人配置的锁,再奉行删除//key存正在,如今值=期冀值时,删除key;key存正在,如今值!=期冀值时,前往0;Longresult=redisTemplate.execute(unlockRedisScript,Collections.singletonList(userId+":syn"),value);logger.info("redis解锁:{}",RELEASE_SUCCESS.equals(result));if(RELEASE_SUCCESS.equals(result)){if(daemonThread!=null){//合上保卫线程daemonThread.stop();logger.info(Thread.currentThread().getName()+":开释锁---");}}}}

 

  个中while轮回中加锁和finally中的开释锁都是基于Lua剧本来竣工了。

   Redis锁的其余要素

  除了上述实例,正在运用Redis散布式锁时,还能够斟酌如下状况及计划。

  Redis锁的不行重入

  当线程正在持有锁的状况下再次恳求加锁,倘若一个锁助助一个线程屡次加锁,那末这个锁即是可重入的。倘若一个不行重入锁被再次加锁,因为该锁仍旧被持有,再次加锁会打击。Redis可经由过程对锁举行重入计数,加锁时加 1,解锁时减 1,当计数归 0时开释锁。

  可重入锁固然高效但会推广代码的庞杂性,这里就不举例评释了。

  等候锁开释

  有的交易场景,挖掘被锁则直接前往。但有的场景下,客户端须要等候锁开释而后去抢锁。上述示例就属于后者。针平等待锁开释也有两种计划:

  客户端轮训:当未得到锁时,等候一段韶华再从新获取,直到胜利。上述示例即是基于这类格式竣工的。这类格式的差池也很光鲜,对比消费任职器资本,当并发量大时会影响任职器的功效。

  运用Redis的定阅颁布效力:当获取锁打击时,定阅锁开释音讯,获取锁胜利后开释时,发送开释音讯。

  集群中的主备切换和脑裂

  正在Redis包括主从同步的集群安置格式中,倘若主节点挂掉,从节点提拔为主节点。倘若客户端A正在主节点加锁胜利,指令还未同步到从节点,此时主节点挂掉,从节点升为主节点,新的主节点中没有锁的数据。这类状况下,客户端B就大概加锁胜利,从而崭露并发的场景。

  当集群发作脑裂时,Redis master节点跟slave 节点和 sentinel 集群处于差别的收集分区。sentinel集群无奈感知到master的存正在,会将 slave 节点提拔为 master 节点,此时就会存正在两个差别的 master 节点。从而也会招致并发成绩的崭露。Redis Cluster集群安置格式同理。

   小结

  经由过程坐褥境遇中的一个成绩,排查出处,寻觅管理计划,到终极对基于Redis散布式的深切斟酌,这就是进修的进程。

  同时,每当口试或被成绩怎样管理散布式同享资本时,咱们会脱口而出基于Redis竣工散布式锁,但经由过程本文的进修会挖掘,Redis散布式锁并非全能的,并且正在运用的过程当中还须要留神超时、死锁、误会锁、集群选主/脑裂等成绩。

  Redis以高功能著称,但正在竣工散布式锁的过程当中仍是存正在极少成绩。于是,基于Redis的散布式锁能够极大的减缓并发成绩,但要所有防御并发,仍是得从数据库层面动手。

  源码地点:https://github.com/secbr/springboot-all/tree/master/springboot-redis-lock

  参考作品:

  https://jinzhihong.github.io/2019/08/12/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA-Redis-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E7%8E%B0-%E4%B8%80/

  https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/

文章推荐:

cba大白熊是谁

直播欧冠预选赛赛程

大地欧洲杯直播

cctv怎么看欧洲杯直播表