自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

又長又細(xì),萬字長文帶你解讀Redisson分布式鎖的源碼

開發(fā) 前端 分布式 Redis
上一篇文章寫了Redis分布式鎖的原理和缺陷,覺得有些不過癮,只是簡單的介紹了下Redisson這個(gè)框架,具體的原理什么的還沒說過呢。趁年后暫時(shí)沒什么事,反正閑著也是閑著,不如把Redisson的源碼也學(xué)習(xí)一遍好了。

[[382196]]

 前言

上一篇文章寫了Redis分布式鎖的原理和缺陷,覺得有些不過癮,只是簡單的介紹了下Redisson這個(gè)框架,具體的原理什么的還沒說過呢。趁年后暫時(shí)沒什么事,反正閑著也是閑著,不如把Redisson的源碼也學(xué)習(xí)一遍好了。

雖說是一時(shí)興起,但仔細(xì)研究之后發(fā)現(xiàn)Redisson的源碼解讀工作量還是挺大的,其中用到了大量的Java并發(fā)類,并且引用了Netty作為通信工具,實(shí)現(xiàn)與Redis組件的遠(yuǎn)程調(diào)用,這些知識點(diǎn)如果要全部講解的話不太現(xiàn)實(shí),本文的重點(diǎn)主要是關(guān)于Redisson分布式鎖的實(shí)現(xiàn)原理,所以網(wǎng)絡(luò)通信和并發(fā)原理這塊的代碼解讀不會太仔細(xì),有不足之處還望見諒!

Redis 發(fā)布訂閱

之前說過,分布式鎖的核心功能其實(shí)就三個(gè):加鎖、解鎖、設(shè)置鎖超時(shí)。這三個(gè)功能也是我們研究Redisson分布式鎖原理的方向。

在學(xué)習(xí)之前,我們有必要先了解一個(gè)知識點(diǎn),就是有關(guān)Redis的發(fā)布訂閱功能。

Redis 發(fā)布訂閱 (pub/sub) 是一種消息通信模式:發(fā)送者 (pub) 發(fā)送消息,訂閱者 (sub) 接收消息,發(fā)布者可以向指定的渠道 (channel) 發(fā)送消息,訂閱者如果訂閱了該頻道的話就能收到消息,從而實(shí)現(xiàn)多個(gè)客戶端的通信效果。


訂閱的命令是SUBSCRIBE channel[channel ...],可以訂閱一個(gè)或多個(gè)頻道,當(dāng)有新消息通過PUBLISH命令發(fā)送給頻道時(shí),訂閱者就能收到消息,就好像這樣:


開啟兩個(gè)客戶端,一個(gè)訂閱了頻道channel1,另一個(gè)通過PUBLISH發(fā)送消息后,訂閱的那個(gè)就能收到了,靠這種模式就能實(shí)現(xiàn)不同客戶端之間的通信。

關(guān)于這種通信模式有哪些妙用場景我們就不展開了,大家可以自己去網(wǎng)上查閱學(xué)習(xí)一下,我們的主角還是Redisson,熱身完畢,該上主菜了。

Redisson源碼

在使用Redisson加鎖之前,需要先獲取一個(gè)RLock實(shí)例對象,有了這個(gè)對象就可以調(diào)用lock、tryLock方法來完成加鎖的功能

  1. Config config = new Config(); 
  2. config.useSingleServer() 
  3.   .setPassword(""
  4.   .setAddress("redis://127.0.0.1:6379"); 
  5. RedissonClient redisson = Redisson.create(config); 
  6. // RLock對象 
  7. RLock lock = redisson.getLock("myLock"); 

配置好對應(yīng)的host,然后就可以創(chuàng)建一個(gè)RLock對象。RLock是一個(gè)接口,具體的同步器需要實(shí)現(xiàn)該接口,當(dāng)我們調(diào)用redisson.getLock()時(shí),程序會初始化一個(gè)默認(rèn)的同步執(zhí)行器RedissonLock


這里面初始化了幾個(gè)參數(shù),

commandExecutor:異步的Executor執(zhí)行器,Redisson中所有的命令都是通過...Executor 執(zhí)行的 ;

id:唯一ID,初始化的時(shí)候是用UUID創(chuàng)建的;

internalLockLeaseTime:等待獲取鎖時(shí)間,這里讀的是配置類中默認(rèn)定義的,時(shí)間為30秒;

同時(shí),圖片里我還標(biāo)注了一個(gè)方法getEntryName,返回的是 “ID :鎖名稱” 的字符串,代表的是當(dāng)前線程持有對應(yīng)鎖的一個(gè)標(biāo)識,這些參數(shù)有必要留個(gè)印象,后面的源碼解析中經(jīng)常會出現(xiàn)。

說完了初始化的東西,我們就可以開始學(xué)習(xí)加鎖和解鎖的源碼了。

加鎖

Redisson的加鎖方法有兩個(gè),tryLock和lock,使用上的區(qū)別在于tryLock可以設(shè)置鎖的過期時(shí)長leaseTime和等待時(shí)長waitTime,核心處理的邏輯都差不多,我們先從tryLock講起。

tryLock

代碼有點(diǎn)長啊。。。整成圖片不太方便,直接貼上來吧,

  1. /** 
  2.  * @param waitTime 等待鎖的時(shí)長  
  3.  * @param leaseTime 鎖的持有時(shí)間  
  4.  * @param unit 時(shí)間單位 
  5.  * @return 
  6.  * @throws InterruptedException 
  7.  */ 
  8. public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {    // 剩余的等待鎖的時(shí)間 
  9.         long time = unit.toMillis(waitTime); 
  10.         long current = System.currentTimeMillis(); 
  11.          
  12.         final long threadId = Thread.currentThread().getId(); 
  13.         // 嘗試獲取鎖,如果沒取到鎖,則返回鎖的剩余超時(shí)時(shí)間 
  14.         Long ttl = tryAcquire(leaseTime, unit, threadId); 
  15.         // ttl為null,說明可以搶到鎖了,返回true 
  16.         if (ttl == null) { 
  17.             return true
  18.         } 
  19.          
  20.         // 如果waitTime已經(jīng)超時(shí)了,就返回false,代表申請鎖失敗 
  21.         time -= (System.currentTimeMillis() - current); 
  22.         if (time <= 0) { 
  23.             acquireFailed(threadId); 
  24.             return false
  25.         } 
  26.          
  27.         current = System.currentTimeMillis(); 
  28.         // 訂閱分布式鎖, 解鎖時(shí)進(jìn)行通知,看,這里就用到了我們上面說的發(fā)布-訂閱了吧 
  29.         final RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId); 
  30.         // 阻塞等待鎖釋放,await()返回false,說明等待超時(shí)了 
  31.         if (!await(subscribeFuture, time, TimeUnit.MILLISECONDS)) { 
  32.             if (!subscribeFuture.cancel(false)) { 
  33.                 subscribeFuture.addListener(new FutureListener<RedissonLockEntry>() { 
  34.                     @Override 
  35.                     public void operationComplete(Future<RedissonLockEntry> future) throws Exception { 
  36.                         if (subscribeFuture.isSuccess()) { 
  37.                          // 等待都超時(shí)了,直接取消訂閱 
  38.                             unsubscribe(subscribeFuture, threadId); 
  39.                         } 
  40.                     } 
  41.                 }); 
  42.             } 
  43.             acquireFailed(threadId); 
  44.             return false
  45.         } 
  46.  
  47.         try { 
  48.             time -= (System.currentTimeMillis() - current); 
  49.             if (time <= 0) { 
  50.                 acquireFailed(threadId); 
  51.                 return false
  52.             } 
  53.          // 進(jìn)入死循環(huán),反復(fù)去調(diào)用tryAcquire嘗試獲取鎖,跟上面那一段拿鎖的邏輯一樣 
  54.             while (true) { 
  55.                 long currentTime = System.currentTimeMillis(); 
  56.                 ttl = tryAcquire(leaseTime, unit, threadId); 
  57.                 // lock acquired 
  58.                 if (ttl == null) { 
  59.                     return true
  60.                 } 
  61.  
  62.                 time -= (System.currentTimeMillis() - currentTime); 
  63.                 if (time <= 0) { 
  64.                     acquireFailed(threadId); 
  65.                     return false
  66.                 } 
  67.  
  68.                 // waiting for message 
  69.                 currentTime = System.currentTimeMillis(); 
  70.                 if (ttl >= 0 && ttl < time) { 
  71.                     getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); 
  72.                 } else { 
  73.                     getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS); 
  74.                 } 
  75.  
  76.                 time -= (System.currentTimeMillis() - currentTime); 
  77.                 if (time <= 0) { 
  78.                     acquireFailed(threadId); 
  79.                     return false
  80.                 } 
  81.             } 
  82.         } finally { 
  83.             unsubscribe(subscribeFuture, threadId); 
  84.         } 
  85. //        return get(tryLockAsync(waitTime, leaseTime, unit)); 
  86.     } 

代碼還是挺長的,不過流程也就兩步,要么線程拿到鎖返回成功;要么沒拿到鎖并且等待時(shí)間還沒過就繼續(xù)循環(huán)拿鎖,同時(shí)監(jiān)聽鎖是否被釋放。

拿鎖的方法是tryAcquire,傳入的參數(shù)分別是鎖的持有時(shí)間,時(shí)間單位以及代表當(dāng)前線程的ID,跟進(jìn)代碼查看調(diào)用棧,它會調(diào)到一個(gè)叫做tryAcquireAsync的方法:

  1. private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) { 
  2.     return get(tryAcquireAsync(leaseTime, unit, threadId)); 
  3.  
  4. private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) { 
  5.         // 如果有設(shè)置鎖的等待時(shí)長的話,就直接調(diào)用tryLockInnerAsync方法獲取鎖 
  6.         if (leaseTime != -1) { 
  7.             return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); 
  8.         } 
  9.         // 沒有設(shè)置等待鎖的時(shí)長的話,加多一個(gè)監(jiān)聽器,也就是調(diào)用lock.lock()會跑的邏輯,后面會說 
  10.         RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); 
  11.         ttlRemainingFuture.addListener(new FutureListener<Long>() { 
  12.             @Override 
  13.             public void operationComplete(Future<Long> future) throws Exception { 
  14.                 if (!future.isSuccess()) { 
  15.                     return
  16.                 } 
  17.  
  18.                 Long ttlRemaining = future.getNow(); 
  19.                 // lock acquired 
  20.                 if (ttlRemaining == null) { 
  21.                     scheduleExpirationRenewal(threadId); 
  22.                 } 
  23.             } 
  24.         }); 
  25.         return ttlRemainingFuture; 
  26.     } 

我們繼續(xù)跟,看看tryLockInnerAsync方法的源碼:

  1. <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { 
  2.     internalLockLeaseTime = unit.toMillis(leaseTime); 
  3.  
  4.     return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command, 
  5.               "if (redis.call('exists', KEYS[1]) == 0) then " + 
  6.                   "redis.call('hset', KEYS[1], ARGV[2], 1); " + 
  7.                   "redis.call('pexpire', KEYS[1], ARGV[1]); " + 
  8.                   "return nil; " + 
  9.               "end; " + 
  10.               "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + 
  11.                   "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + 
  12.                   "redis.call('pexpire', KEYS[1], ARGV[1]); " + 
  13.                   "return nil; " + 
  14.               "end; " + 
  15.               "return redis.call('pttl', KEYS[1]);"
  16.                 Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); 
  17. String getLockName(long threadId) { 
  18.     return id + ":" + threadId; 

這里就是底層的調(diào)用棧了,直接操作命令,整合成lua腳本后,調(diào)用netty的工具類跟redis進(jìn)行通信,從而實(shí)現(xiàn)獲取鎖的功能。

這段腳本命令還是有點(diǎn)意思的,簡單解讀一下:

  • 先用exists key命令判斷是否鎖是否被占據(jù)了,沒有的話就用hset命令寫入,key為鎖的名稱,field為“客戶端唯一ID:線程ID”,value為1;
  • 鎖被占據(jù)了,判斷是否是當(dāng)前線程占據(jù)的,是的話value值加1;
  • 鎖不是被當(dāng)前線程占據(jù),返回鎖剩下的過期時(shí)長;

命令的邏輯并不復(fù)雜,但不得不說,作者的設(shè)計(jì)還是很有心的,用了redis的Hash結(jié)構(gòu)存儲數(shù)據(jù),如果發(fā)現(xiàn)當(dāng)前線程已經(jīng)持有鎖了,就用hincrby命令將value值加1,value的值將決定釋放鎖的時(shí)候調(diào)用解鎖命令的次數(shù),達(dá)到實(shí)現(xiàn)鎖的可重入性效果。

每一步命令對應(yīng)的邏輯我都在下面的圖中標(biāo)注了,大家可以讀一下:


我們繼續(xù)跟代碼吧,根據(jù)上面的命令可以看出,如果線程拿到鎖的話,tryLock方法會直接返回true,萬事大吉。

拿不到的話,就會返回鎖的剩余過期時(shí)長,這個(gè)時(shí)長有什么作用呢?我們回到tryLock方法中死循環(huán)的那個(gè)地方:


這里有一個(gè)針對waitTime和key的剩余過期時(shí)間大小的比較,取到二者中比較小的那個(gè)值,然后用Java的Semaphore信號量的tryAcquire方法來阻塞線程。

那么Semaphore信號量又是由誰控制呢,何時(shí)才能release呢。這里又需要回到上面來看,各位看官應(yīng)該還記得,我們上面貼的tryLock代碼中還有這一段:

  1. current = System.currentTimeMillis(); 
  2. // 訂閱分布式鎖, 解鎖時(shí)進(jìn)行通知 
  3. final RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId); 

訂閱的邏輯顯然是在subscribe方法里,跟著方法的調(diào)用鏈,它會進(jìn)入到PublishSubscribe.Java中:


這段代碼的作用在于將當(dāng)前線程的threadId添加到一個(gè)AsyncSemaphore中,并且設(shè)置一個(gè)redis的監(jiān)聽器,這個(gè)監(jiān)聽器是通過redis的發(fā)布、訂閱功能實(shí)現(xiàn)的。

一旦監(jiān)聽器收到redis發(fā)來的消息,就從中獲取與當(dāng)前thread相關(guān)的,如果是鎖被釋放的消息,就立馬通過操作Semaphore(也就是調(diào)用release方法)來讓剛才阻塞的地方釋放。


釋放后線程繼續(xù)執(zhí)行,仍舊是判斷是否已經(jīng)超時(shí)。如果還沒超時(shí),就進(jìn)入下一次循環(huán)再次去獲取鎖,拿到就返回true,沒有拿到的話就繼續(xù)流程。

這里說明一下,之所以要循環(huán),是因?yàn)殒i可能會被多個(gè)客戶端同時(shí)爭搶,線程阻塞被釋放之后的那一瞬間很可能還是拿不到鎖,但是線程的等待時(shí)間又還沒過,這個(gè)時(shí)候就需要重新跑循環(huán)去拿鎖。

這就是tryLock獲取鎖的整個(gè)過程了,畫一張流程圖的話表示大概是這樣:


lock

除了tryLock,一般我們還經(jīng)常直接調(diào)用lock來獲取鎖,lock的拿鎖過程跟tryLock基本是一致的,區(qū)別在于lock沒有手動設(shè)置鎖過期時(shí)長的參數(shù),該方法的調(diào)用鏈也是跑到tryAcquire方法來獲取鎖的,不同的是,它會跑到這部分的邏輯:


這段代碼做了兩件事:

1、預(yù)設(shè)30秒的過期時(shí)長,然后去獲取鎖

2、開啟一個(gè)監(jiān)聽器,如果發(fā)現(xiàn)拿到鎖了,就開啟定時(shí)任務(wù)不斷去刷新該鎖的過期時(shí)長

刷新過期時(shí)長的方法是scheduleExpirationRenewal,貼一下源碼吧:

  1. private void scheduleExpirationRenewal(final long threadId) { 
  2.  // expirationRenewalMap是一個(gè)ConcurrentMap,存儲標(biāo)志為"當(dāng)前線程ID:key名稱"的任務(wù) 
  3.         if (expirationRenewalMap.containsKey(getEntryName())) { 
  4.             return
  5.         } 
  6.  
  7.         Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { 
  8.             @Override 
  9.             public void run(Timeout timeout) throws Exception { 
  10.                 // 檢測鎖是否存在的lua腳本,存在的話就用pexpire命令刷新過期時(shí)長 
  11.                 RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, 
  12.                         "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + 
  13.                             "redis.call('pexpire', KEYS[1], ARGV[1]); " + 
  14.                             "return 1; " + 
  15.                         "end; " + 
  16.                         "return 0;"
  17.                           Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); 
  18.                  
  19.                 future.addListener(new FutureListener<Boolean>() { 
  20.                     @Override 
  21.                     public void operationComplete(Future<Boolean> future) throws Exception { 
  22.                         expirationRenewalMap.remove(getEntryName()); 
  23.                         if (!future.isSuccess()) { 
  24.                             log.error("Can't update lock " + getName() + " expiration", future.cause()); 
  25.                             return
  26.                         } 
  27.                          
  28.                         if (future.getNow()) { 
  29.                             // reschedule itself 
  30.                             scheduleExpirationRenewal(threadId); 
  31.                         } 
  32.                     } 
  33.                 }); 
  34.             } 
  35.         }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); 
  36.  
  37.         if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) { 
  38.             task.cancel(); 
  39.         } 
  40.     } 

代碼的流程比較簡單,大概就是開啟一個(gè)定時(shí)任務(wù),每隔internalLockLeaseTime / 3的時(shí)間(這個(gè)時(shí)間是10秒)就去檢測鎖是否還被當(dāng)前線程持有,是的話就重新設(shè)置過期時(shí)長internalLockLeaseTime,也就是30秒的時(shí)間。

而這些定時(shí)任務(wù)會存儲在一個(gè)ConcurrentHashMap對象expirationRenewalMap中,存儲的key就為“線程ID:key名稱”,如果發(fā)現(xiàn)expirationRenewalMap中不存在對應(yīng)當(dāng)前線程key的話,定時(shí)任務(wù)就不會跑,這也是后面解鎖中的一步重要操作。

上面這段代碼就是Redisson中所謂的”看門狗“程序,用一個(gè)異步線程來定時(shí)檢測并執(zhí)行的,以防手動解鎖之前就過期了。

其他的邏輯就跟tryLock()基本沒什么兩樣啦,大家看一下就知道了

解鎖

有拿鎖的方法,自然也就有解鎖。Redisson分布式鎖解鎖的上層調(diào)用方法是unlock(),默認(rèn)不用傳任何參數(shù)

  1. @Override 
  2.     public void unlock() { 
  3.      // 發(fā)起釋放鎖的命令請求 
  4.         Boolean opStatus = get(unlockInnerAsync(Thread.currentThread().getId())); 
  5.         if (opStatus == null) { 
  6.             throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " 
  7.                     + id + " thread-id: " + Thread.currentThread().getId()); 
  8.         } 
  9.         if (opStatus) { 
  10.          // 成功釋放鎖,取消"看門狗"的續(xù)時(shí)線程 
  11.             cancelExpirationRenewal(); 
  12.         } 
  13.     } 

解鎖相關(guān)的命令操作在unlockInnerAsync方法中定義,

 又是一大串的lua腳本,比起前面加鎖那段腳本的命令稍微復(fù)雜了點(diǎn),不過沒關(guān)系,我們簡單梳理一下,命令的邏輯大概是這么幾步:

1、判斷鎖是否存在,不存在的話用publish命令發(fā)布釋放鎖的消息,訂閱者收到后就能做下一步的拿鎖處理;

2、鎖存在但不是當(dāng)前線程持有,返回空置nil;

3、當(dāng)前線程持有鎖,用hincrby命令將鎖的可重入次數(shù)-1,然后判斷重入次數(shù)是否大于0,是的話就重新刷新鎖的過期時(shí)長,返回0,否則就刪除鎖,并發(fā)布釋放鎖的消息,返回1;

 當(dāng)線程完全釋放鎖后,就會調(diào)用cancelExpirationRenewal()方法取消"看門狗"的續(xù)時(shí)線程

  1. void cancelExpirationRenewal() { 
  2.  // expirationRenewalMap移除對應(yīng)的key,就不會執(zhí)行當(dāng)前線程對應(yīng)的"看門狗"程序了 
  3.     Timeout task = expirationRenewalMap.remove(getEntryName()); 
  4.     if (task != null) { 
  5.         task.cancel(); 
  6.     } 

這就是釋放鎖的過程了,怎么樣,是不是還是比較簡單的,閱讀起來比加鎖那份代碼舒服多了,當(dāng)然啦,簡單歸簡單,為了方便你們理清整個(gè)分布式鎖的過程,我當(dāng)然還是費(fèi)心費(fèi)力的給你們畫流程圖展示下啦(就沖這點(diǎn),是不是該給我來個(gè)三連啊,哈哈):


RedLock

以上就是Redisson分布式鎖的原理講解,總的來說,就是簡單的用lua腳本整合基本的set命令實(shí)現(xiàn)鎖的功能,這也是很多Redis分布式鎖工具的設(shè)計(jì)原理。除此之外,Redisson還支持用"RedLock算法"來實(shí)現(xiàn)鎖的效果,這個(gè)工具類就是RedissonRedLock。


用法也很簡單,創(chuàng)建多個(gè)Redisson Node, 由這些無關(guān)聯(lián)的Node就可以組成一個(gè)完整的分布式鎖

  1. RLock lock1 = Redisson.create(config1).getLock(lockKey); 
  2. RLock lock2 = Redisson.create(config2).getLock(lockKey); 
  3. RLock lock3 = Redisson.create(config3).getLock(lockKey); 
  4.  
  5. RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3); 
  6. try { 
  7.    redLock.lock(); 
  8. } finally { 
  9.    redLock.unlock(); 

RedLock算法原理方面我就不細(xì)說了,大家有興趣可以看我之前的文章,或者是網(wǎng)上搜一下,簡單的說就是能一定程度上能有效防止Redis實(shí)例單點(diǎn)故障的問題,但并不完全可靠,不管是哪種設(shè)計(jì),光靠Redis本身都是無法保證鎖的強(qiáng)一致性的。

還是那句話,魚和熊掌不可兼得,性能和安全方面也往往如此,Redis強(qiáng)大的性能和使用的方便足以滿足日常的分布式鎖需求,如果業(yè)務(wù)場景對鎖的安全隱患無法忍受的話,最保底的方式就是在業(yè)務(wù)層做冪等處理。

總結(jié)

看了本文的源碼解析,相信各位看官對Redisson分布式鎖的設(shè)計(jì)也有了足夠的了解,當(dāng)然啦,雖然是講解源碼,我們的主要精力還是放在分布式鎖的原理上,一些無關(guān)流程的代碼就沒有帶大家字斟酌句的解讀了,大家有興趣的話可以自己去閱讀看看,源碼中很多地方都展示了一些基礎(chǔ)并發(fā)工具和網(wǎng)絡(luò)通信的妙用之處,學(xué)習(xí)一下還是挺有收獲的。

最后我還是想吐槽一下,Redisson的注釋是真的少啊。。。。。。

 

 

責(zé)任編輯:姜華 來源: 鄙人薛某
相關(guān)推薦

2021-08-26 05:02:50

分布式設(shè)計(jì)

2022-09-06 08:02:40

死鎖順序鎖輪詢鎖

2022-09-14 09:01:55

shell可視化

2020-07-09 07:54:35

ThreadPoolE線程池

2022-07-19 16:03:14

KubernetesLinux

2021-10-18 11:58:56

負(fù)載均衡虛擬機(jī)

2021-01-19 05:49:44

DNS協(xié)議

2021-07-06 08:37:29

Redisson分布式

2021-02-26 05:17:38

計(jì)算機(jī)網(wǎng)絡(luò)二進(jìn)制

2024-03-07 18:11:39

Golang采集鏈接

2023-12-04 08:10:34

Spring循環(huán)依賴

2020-07-15 08:57:40

HTTPSTCP協(xié)議

2020-11-16 10:47:14

FreeRTOS應(yīng)用嵌入式

2023-06-12 08:49:12

RocketMQ消費(fèi)邏輯

2021-07-02 08:51:09

Redisson分布式鎖公平鎖

2022-10-10 08:35:17

kafka工作機(jī)制消息發(fā)送

2021-06-30 14:56:12

Redisson分布式公平鎖

2024-05-10 12:59:58

PyTorch人工智能

2024-05-20 08:00:00

TiDB數(shù)據(jù)庫分布式數(shù)據(jù)庫

2024-07-15 07:55:00

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號