最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redisson分布式鎖之加解鎖詳解

 更新時(shí)間:2023年03月16日 09:16:45   作者:宮三公子  
這篇文章主要為大家介紹了Redisson分布式鎖加解鎖的詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

2023的金三銀四來的沒想象中那么激烈,一個(gè)朋友前段時(shí)間投了幾十家,多數(shù)石沉大海,好不容易等來面試機(jī)會(huì),就恰好被問道項(xiàng)目中關(guān)于分布式鎖的應(yīng)用,后涉及Redisson實(shí)現(xiàn)分布式鎖的原理,答不上來。

鎖的可重入性

我們都知道,Java中synchronized和lock都支持可重入,synchronized的鎖關(guān)聯(lián)一個(gè)線程持有者和一個(gè)計(jì)數(shù)器。當(dāng)一個(gè)線程請求成功后,JVM會(huì)記下持有鎖的線程,并將計(jì)數(shù)器計(jì)為1。此時(shí)其他線程請求該鎖,則必須等待。而該持有鎖的線程如果再次請求這個(gè)鎖,就可以再次拿到這個(gè)鎖,同時(shí)計(jì)數(shù)器會(huì)遞增。當(dāng)線程退出一個(gè)synchronized方法/塊時(shí),計(jì)數(shù)器會(huì)遞減,如果計(jì)數(shù)器為0則釋放該鎖;在ReentrantLock中,底層的 AQS 對應(yīng)的state 同步狀態(tài)值表示線程獲取該鎖的可重入次數(shù),通過CAS方式進(jìn)行設(shè)置,在默認(rèn)情況下,state的值為0 表示當(dāng)前鎖沒有被任何線程持有,原理類似。所以如果想要實(shí)現(xiàn)可重入性,可能須有一個(gè)計(jì)數(shù)器來控制重入次數(shù),實(shí)際Redisson確實(shí)是這么做的。

好的我們通過Redisson客戶端進(jìn)行設(shè)置,并循環(huán)3次,模擬鎖重入:000

for(int i = 0; i < 3; i++) {      
    RedissonLockUtil.tryLock("distributed:lock:distribute_key", TimeUnit.SECONDS, 20, 100); 
 }

連接Redis客戶端進(jìn)行查看:

可以看到,我們設(shè)置的分布式鎖是存在一個(gè)hash結(jié)構(gòu)中,value看起來是循環(huán)的次數(shù)3,key就不怎么認(rèn)識(shí)了,那這個(gè)key是怎么設(shè)置進(jìn)去的呢,另外為什么要設(shè)置成為Hash類型呢?

加鎖

我們先來看看普通的分布式鎖的上鎖流程:

說明:

  • 客戶端在進(jìn)行加鎖時(shí),會(huì)校驗(yàn)如果業(yè)務(wù)上沒有設(shè)置持有鎖時(shí)長leaseTime,會(huì)啟動(dòng)看門狗來每隔10s進(jìn)行續(xù)命,否則就直接以leaseTime作為持有的時(shí)長;
  • 并發(fā)場景下,如果客戶端1鎖還未釋放,客戶端2嘗試獲取,加鎖必然失敗,然后會(huì)通過發(fā)布訂閱模式來訂閱Key的釋放通知,并繼續(xù)進(jìn)入后續(xù)的搶鎖流程。
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
      long time = unit.toMillis(waitTime);
      long current = System.currentTimeMillis();
      long threadId = Thread.currentThread().getId();
      Long ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
      if (ttl == null) {
         return true;
      } else {
         // 訂閱分布式Key對應(yīng)的消息,監(jiān)聽其它鎖持有者釋放,鎖沒有釋放的時(shí)候則會(huì)等待,直到鎖釋放的時(shí)候會(huì)執(zhí)行下面的while循環(huán)
         CompletableFuture subscribeFuture = this.subscribe(threadId);
         subscribeFuture.get(time, TimeUnit.MILLISECONDS);
         try {
            do {
               // 嘗試獲取鎖
               ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
               // 競爭獲取鎖成功,退出循環(huán),不再競爭。
               if (ttl == null) {
                  return true;
               }
               // 利用信號(hào)量機(jī)制阻塞當(dāng)前線程相應(yīng)時(shí)間,之后再重新獲取鎖
               if (ttl >= 0L && ttl < time) {
                  ((RedissonLockEntry)this.commandExecutor.getNow(subscribeFuture)).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
               } else {
                  ((RedissonLockEntry)this.commandExecutor.getNow(subscribeFuture)).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
               }
               time -= System.currentTimeMillis() - currentTime;
            } while(time > 0L);
         } finally {
            // 競爭鎖成功后,取消訂閱該線程Id事件
            this.unsubscribe((RedissonLockEntry)this.commandExecutor.getNow(subscribeFuture), threadId);
         }
      }
   }
}
RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {
        // 如果設(shè)置了持有鎖的時(shí)長,直接進(jìn)行嘗試加鎖操作
         if (leaseTime != -1L) {
            return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
            // 未設(shè)置加鎖時(shí)長,在加鎖成功后,啟動(dòng)續(xù)期任務(wù),初始默認(rèn)持有鎖時(shí)間是30s
            RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
            ttlRemainingFuture.addListener(new FutureListener<Long>() {
                public void operationComplete(Future<Long> future) throws Exception {
                    if (future.isSuccess()) {
                        Long ttlRemaining = (Long)future.getNow();
                        if (ttlRemaining == null) {
                            RedissonLock.this.scheduleExpirationRenewal(threadId);
                        }
                    }
                }
            });
            return ttlRemainingFuture;
        }
    }

我們都知道Redis執(zhí)行Lua腳本具有原子性,所以在嘗試加鎖的下層,Redis主要執(zhí)行了一段復(fù)雜的lua腳本:

-- 不存在該key時(shí)
if (redis.call('exists', KEYS[1]) == 0) then
      -- 新增該鎖并且hash中該線程id對應(yīng)的count置1
redis.call('hincrby', KEYS[1], ARGV[2], 1);
-- 設(shè)置過期時(shí)間
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
-- 存在該key 并且 hash中線程id的key也存在
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
      -- 線程重入次數(shù)++
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
return redis.call('pttl', KEYS[1]);

參數(shù)說明:

KEYS[1]:對應(yīng)我們設(shè)置的分布式key,即:distributed:lock:distribute_key

ARGV[1]:業(yè)務(wù)自定義的加鎖時(shí)長或者默認(rèn)的30s;

ARGV[2]: 具體的客戶端初始化連接UUID+線程ID: 9d8f0907-1165-47d2-8983-1e130b07ad0c:1

我們從上面的腳本中可以看出核心邏輯其實(shí)不難:

  • 如果分布式鎖Key未被任何端持有,直接根據(jù)“客戶端連接ID+線程ID” 進(jìn)行初始化設(shè)置,并設(shè)置重入次數(shù)為1,并設(shè)置Key的過期時(shí)間;
  • 否則重入次數(shù)+1,并重置過期時(shí)間;

鎖續(xù)命

接下來看看scheduleExpirationRenewal續(xù)命是怎么做的呢?

private void scheduleExpirationRenewal(final long threadId) {
   if (!expirationRenewalMap.containsKey(this.getEntryName())) {
      Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
         public void run(Timeout timeout) throws Exception {
            // 執(zhí)行續(xù)命操作
            RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
            future.addListener(new FutureListener<Boolean>() {
               public void operationComplete(Future<Boolean> future) throws Exception {
                  RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName());
                          ...
                  // 續(xù)命成功,繼續(xù)
                  if ((Boolean)future.getNow()) {
                     RedissonLock.this.scheduleExpirationRenewal(threadId);
                  }
               }
            });
         }
      }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
   }
}

Tip小知識(shí)點(diǎn):

  • 續(xù)期是用的什么定時(shí)任務(wù)執(zhí)行的?
    Redisson用netty的HashedWheelTimer做命令重試機(jī)制,原因在于一條redis命令的執(zhí)行不論成功或者失敗耗時(shí)都很短,而HashedWheelTimer是單線程的,系統(tǒng)性能開銷小。

而在上面的renewExpirationAsync中續(xù)命操作的執(zhí)行核心Lua腳本要做的事情也非常的簡單,就是給這個(gè)Key的過期時(shí)間重新設(shè)置為指定的30s.

if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return 1;
end;
return 0;

釋放鎖

釋放鎖主要是除了解鎖本省,另外還要考慮到如果存在續(xù)期的情況,要將續(xù)期任務(wù)刪除:

public RFuture<Void> unlockAsync(long threadId) {
   // 解鎖
   RFuture<Boolean> future = this.unlockInnerAsync(threadId);
   CompletionStage<Void> f = future.handle((opStatus, e) -> {
      // 解除續(xù)期
      this.cancelExpirationRenewal(threadId);
      ...
   });
   return new CompletableFutureWrapper(f);
}

在unlockInnerAsync內(nèi)部,Redisson釋放鎖其實(shí)核心也是執(zhí)行了如下一段核心Lua腳本:

    // 校驗(yàn)是否存在
    if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
      return nil;
      end;
    // 獲取加鎖次數(shù),校驗(yàn)是否為重入鎖
    local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
    // 如果為重入鎖,重置過期時(shí)間,鎖本身不釋放
    if (counter > 0) then
      redis.call('pexpire', KEYS[1], ARGV[2]);
      return 0;
   // 刪除Key
    else redis.call('del', KEYS[1]);
      // 通知阻塞的客戶端可以搶鎖啦
      redis.call('publish', KEYS[2], ARGV[1]);
      return 1;
      end;
      return nil;

其中:

KEYS[1]: 分布式鎖
KEYS[2]: redisson_lock_channel:{分布式鎖} 發(fā)布訂閱消息的管道名稱
ARGV[1]: 發(fā)布的消息內(nèi)容
ARGV[2]: 鎖的過期時(shí)間
ARGV[3]: 線程ID標(biāo)識(shí)名稱

其它問題

  • 紅鎖這么火,但真的靠譜么?
  • Redisson公平鎖是什么情況?

以上就是Redisson分布式鎖第一彈-加解鎖的詳細(xì)內(nèi)容,更多關(guān)于Redisson分布式鎖加解鎖的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • antd為Tree組件標(biāo)題附加操作按鈕功能

    antd為Tree組件標(biāo)題附加操作按鈕功能

    這篇文章主要介紹了antd為Tree組件標(biāo)題附加操作按鈕功能,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-08-08
  • win 7 安裝redis服務(wù)【筆記】

    win 7 安裝redis服務(wù)【筆記】

    Redis是一個(gè)開源的使用ANSI C語言編寫、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API。
    2016-05-05
  • Redis多種內(nèi)存淘汰策略及配置技巧分享

    Redis多種內(nèi)存淘汰策略及配置技巧分享

    本文介紹了 Redis 內(nèi)存滿時(shí)的淘汰機(jī)制,包括內(nèi)存淘汰機(jī)制的概念,Redis 提供的 8 種淘汰策略(如 noeviction、volatile-lru 等)及其適用場景,還講解了如何配置淘汰機(jī)制,通過合理配置可提高緩存效率和系統(tǒng)性能,需要的朋友可以參考下
    2025-01-01
  • 動(dòng)態(tài)添加Redis密碼認(rèn)證的方法

    動(dòng)態(tài)添加Redis密碼認(rèn)證的方法

    本篇文章主要介紹了動(dòng)態(tài)添加Redis密碼認(rèn)證的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-06-06
  • 從源碼解讀redis持久化

    從源碼解讀redis持久化

    redis的持久化也就是數(shù)據(jù)落地,對于任何一個(gè)數(shù)據(jù)系統(tǒng)都要考慮是不是需要數(shù)據(jù)落地。在系統(tǒng)崩潰或是機(jī)房掉電等的情況下,將有用的數(shù)據(jù)記錄在非易失性存儲(chǔ)器上面,防止數(shù)據(jù)丟失,以及用來系統(tǒng)重啟時(shí)的數(shù)據(jù)恢復(fù)。
    2018-08-08
  • Redis實(shí)現(xiàn)IP限流的2種方式舉例詳解

    Redis實(shí)現(xiàn)IP限流的2種方式舉例詳解

    通俗的說限流就是限制一段時(shí)間內(nèi)用戶訪問資源的次數(shù),減輕服務(wù)器壓力,這篇文章主要給大家介紹了關(guān)于Redis實(shí)現(xiàn)IP限流的2種方式,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2024-08-08
  • Redis數(shù)據(jù)庫的使用場景介紹(避免誤用Redis)

    Redis數(shù)據(jù)庫的使用場景介紹(避免誤用Redis)

    這篇文章主要介紹了Redis數(shù)據(jù)庫的使用場景介紹(避免誤用Redis),本文用簡要的語言總結(jié)了Redis數(shù)據(jù)庫的適應(yīng)場合,人而避免錯(cuò)誤的使用它而產(chǎn)生昂貴的維護(hù)代價(jià),需要的朋友可以參考下
    2015-03-03
  • Redis事務(wù)與數(shù)據(jù)持久化方式

    Redis事務(wù)與數(shù)據(jù)持久化方式

    該文檔主要介紹了Redis事務(wù)和持久化機(jī)制,事務(wù)通過將多個(gè)命令打包執(zhí)行,而持久化則通過快照(RDB)和追加式文件(AOF)兩種方式將內(nèi)存數(shù)據(jù)保存到磁盤,以防止數(shù)據(jù)丟失
    2025-01-01
  • 關(guān)于redis可視化工具讀取數(shù)據(jù)亂碼問題

    關(guān)于redis可視化工具讀取數(shù)據(jù)亂碼問題

    大家來聊一聊在日常操作redis時(shí)用的是什么工具,redis提供的一些命令你都了解了嗎,今天通過本文給大家介紹redis可視化工具讀取數(shù)據(jù)亂碼問題,感興趣的朋友跟隨小編一起看看吧
    2021-07-07
  • redis數(shù)據(jù)結(jié)構(gòu)之壓縮列表

    redis數(shù)據(jù)結(jié)構(gòu)之壓縮列表

    這篇文章主要介紹了redis數(shù)據(jù)結(jié)構(gòu)之壓縮列表,壓縮列表是列表list和hash數(shù)據(jù)結(jié)構(gòu)的底層實(shí)現(xiàn)之一,是redis為了節(jié)約內(nèi)存而開發(fā)的,由一系列特殊編碼的連續(xù)內(nèi)存塊組成的順序型數(shù)據(jù)結(jié)構(gòu),下面詳細(xì)內(nèi)容需要的小伙伴可以參考一下
    2022-03-03

最新評(píng)論

白沙| 三门峡市| 广宁县| 惠来县| 饶阳县| 开鲁县| 东平县| 衡阳市| 沈阳市| 鄱阳县| 宁海县| 通许县| 西吉县| 本溪| 新营市| 南木林县| 高邑县| 湟源县| 漯河市| 博白县| 汪清县| 新民市| 科技| 密云县| 买车| 和顺县| 合江县| 鄂托克旗| 稻城县| 三原县| 凤阳县| 微山县| 砀山县| 雅江县| 乐平市| 永嘉县| 邵阳市| 昂仁县| 巴林左旗| 揭东县| 敦煌市|