中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Redisson?RedLock紅鎖加鎖實現過程及原理是什么

發布時間:2023-02-13 09:25:26 來源:億速云 閱讀:134 作者:iii 欄目:開發技術

本篇內容介紹了“Redisson RedLock紅鎖加鎖實現過程及原理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

一、主從redis架構中分布式鎖存在的問題

1、線程A從主redis中請求一個分布式鎖,獲取鎖成功;

2、從redis準備從主redis同步鎖相關信息時,主redis突然發生宕機,鎖丟失了;

3、觸發從redis升級為新的主redis;

4、線程B從繼任主redis的從redis上申請一個分布式鎖,此時也能獲取鎖成功;

5、導致,同一個分布式鎖,被兩個客戶端同時獲取,沒有保證獨占使用特性;

為了解決這個問題,redis引入了紅鎖的概念。

二、紅鎖算法原理

需要準備多臺redis實例,這些redis實例指的是完全互相獨立的Redis節點,這些節點之間既沒有主從,也沒有集群關系。客戶端申請分布式鎖的時候,需要向所有的redis實例發出申請,只有超過半數的redis實例報告獲取鎖成功,才能算真正獲取到鎖。

具體的紅鎖算法主要包括如下步驟:

1、應用程序獲取當前系統時間(單位是毫秒);

2、應用程序使用相同的key、value依次嘗試從所有的redis實例申請分布式鎖,這里獲取鎖的嘗試時間要遠遠小于鎖的超時時間,防止某個master Down了,我們還在不斷的獲取鎖,而被阻塞過長的時間;

3、只有超過半數的redis實例反饋獲取鎖成功,并且獲取鎖的總耗時小于鎖的超時時間,才認為鎖獲取成功;

4、如果鎖獲取成功了,鎖的超時時間就是最初的鎖超時時間減去獲取鎖的總耗時時間;

5、如果鎖獲取失敗了,不管是因為獲取成功的redis節點沒有過半,還是因為獲取鎖的總耗時超過了鎖的超時時間,都會向已經獲取鎖成功的redis實例發出刪除對應key的請求,去釋放鎖;

三、紅鎖算法的使用

在Redisson框架中,實現了紅鎖的機制,Redisson的RedissonRedLock對象實現了Redlock介紹的加鎖算法。該對象也可以用來將多個RLock對象關聯為一個紅鎖,每個RLock對象實例可以來自于不同的Redisson實例。當紅鎖中超過半數的RLock加鎖成功后,才會認為加鎖是成功的,這就提高了分布式鎖的高可用。

使用的步驟如下:引入Redisson的maven依賴

<!-- JDK 1.8+ compatible -->
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.9.0</version>
</dependency>

編寫單元測試:

@Test
public void testRedLock() {
    Config config = new Config();
    config.useSingleServer().setAddress("redis://127.0.0.1:6379");
    RedissonClient client1 = Redisson.create(config);
    RLock lock1 = client1.getLock("lock1");
    RLock lock2 = client1.getLock("lock2");
    RLock lock3 = client1.getLock("lock3");
    RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
    try {
        /**
         * 4.嘗試獲取鎖
         * redLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS)
         * waitTimeout 嘗試獲取鎖的最大等待時間,超過這個值,則認為獲取鎖失敗
         * leaseTime   鎖的持有時間,超過這個時間鎖會自動失效(值應設置為大于業務處理的時間,確保在鎖有效期內業務能處理完)
         */
        // 嘗試加鎖,最多等待100秒,上鎖以后10秒自動解鎖
        boolean res = redLock.tryLock(100, 10, TimeUnit.SECONDS);
        if (res) {
            //成功獲得鎖,在這里處理業務
            System.out.println("成功獲取到鎖...");
        }
    } catch (Exception e) {
        throw new RuntimeException("aquire lock fail");
    } finally {
        // 無論如何, 最后都要解鎖
        redLock.unlock();
    }
}

四、紅鎖加鎖流程

RedissonRedLock紅鎖繼承自RedissonMultiLock聯鎖,簡單介紹一下聯鎖:

基于Redis的Redisson分布式聯鎖RedissonMultiLock對象可以將多個RLock對象關聯為一個聯鎖,每個RLock對象實例可以來自于不同的Redisson實例,所有的鎖都上鎖成功才算成功。

RedissonRedLock的加鎖、解鎖代碼都是使用RedissonMultiLock中的方法,只是其重寫了一些方法,如:

failedLocksLimit():允許加鎖失敗節點個數限制。在RedissonRedLock中,必須超過半數加鎖成功才能算成功,其實現為:

protected int failedLocksLimit() {
    return locks.size() - minLocksAmount(locks);
}
protected int minLocksAmount(final List<RLock> locks) {
    // 最小的獲取鎖成功數:n/2 + 1。 過半機制
    return locks.size()/2 + 1;
}

在RedissonMultiLock中,則必須全部都加鎖成功才算成功,所以允許加鎖失敗節點個數為0,其實現為:

protected int failedLocksLimit() {
    return 0;
}

接下來,我們以tryLock()方法為例,詳細分析紅鎖是如何加鎖的,具體代碼如下:

org.redisson.RedissonMultiLock#tryLock(long, long, java.util.concurrent.TimeUnit)

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
//        try {
//            return tryLockAsync(waitTime, leaseTime, unit).get();
//        } catch (ExecutionException e) {
//            throw new IllegalStateException(e);
//        }
    long newLeaseTime = -1;
    if (leaseTime > 0) {
        if (waitTime > 0) {
            newLeaseTime = unit.toMillis(waitTime)*2;
        } else {
            newLeaseTime = unit.toMillis(leaseTime);
        }
    }
    // 獲取當前系統時間,單位:毫秒
    long time = System.currentTimeMillis();
    long remainTime = -1;
    if (waitTime > 0) {
        remainTime = unit.toMillis(waitTime);
    }
    long lockWaitTime = calcLockWaitTime(remainTime);
    // 允許加鎖失敗節點個數限制(N - ( N / 2 + 1 ))
    // 假設有三個redis節點,則failedLocksLimit = 1
    int failedLocksLimit = failedLocksLimit();
    // 存放調用tryLock()方法加鎖成功的那些redis節點
    List<RLock> acquiredLocks = new ArrayList<>(locks.size());
    // 循環所有節點,通過EVAL命令執行LUA腳本進行加鎖
    for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
        // 獲取到其中一個redis實例
        RLock lock = iterator.next();
        String lockName = lock.getName();
        System.out.println("lockName = " + lockName + "正在嘗試加鎖...");
        boolean lockAcquired;
        try {
            // 未指定鎖超時時間和獲取鎖等待時間的情況
            if (waitTime <= 0 && leaseTime <= 0) {
                // 調用tryLock()嘗試加鎖
                lockAcquired = lock.tryLock();
            } else {
                // 指定了超時時間的情況,重新計算獲取鎖的等待時間
                long awaitTime = Math.min(lockWaitTime, remainTime);
                // 調用tryLock()嘗試加鎖
                lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
            }
        } catch (RedisResponseTimeoutException e) {
            // 如果拋出RedisResponseTimeoutException異常,為了防止加鎖成功,但是響應失敗,需要解鎖所有節點
            unlockInner(Arrays.asList(lock));
            // 表示獲取鎖失敗
            lockAcquired = false;
        } catch (Exception e) {
            // 表示獲取鎖失敗
            lockAcquired = false;
        }
        if (lockAcquired) {
            // 如果當前redis節點加鎖成功,則加入到acquiredLocks集合中
            acquiredLocks.add(lock);
        } else {
            // 計算已經申請鎖失敗的節點是否已經到達 允許加鎖失敗節點個數限制 (N-(N/2+1)), 如果已經到達,就認定最終申請鎖失敗,則沒有必要繼續從后面的節點申請了。因為 Redlock 算法要求至少N/2+1 個節點都加鎖成功,才算最終的鎖申請成功
            if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
                break;
            }
            if (failedLocksLimit == 0) {
                unlockInner(acquiredLocks);
                if (waitTime <= 0) {
                    return false;
                }
                failedLocksLimit = failedLocksLimit();
                acquiredLocks.clear();
                // reset iterator
                while (iterator.hasPrevious()) {
                    iterator.previous();
                }
            } else {
                failedLocksLimit--;
            }
        }
        // 計算 目前從各個節點獲取鎖已經消耗的總時間,如果已經等于最大等待時間,則認定最終申請鎖失敗,返回false
        if (remainTime > 0) {
            // remainTime: 鎖剩余時間,這個時間是某個客戶端向所有redis節點申請獲取鎖的總等待時間, 獲取鎖的中耗時時間不能大于這個時間。
            // System.currentTimeMillis() - time: 這個計算出來的就是當前redis節點獲取鎖消耗的時間
            remainTime -= System.currentTimeMillis() - time;
            // 重置time為當前時間,因為下一次循環的時候,方便計算下一個redis節點獲取鎖消耗的時間
            time = System.currentTimeMillis();
            // 鎖剩余時間減到0了,說明達到最大等待時間,加鎖超時,認為獲取鎖失敗,需要對成功加鎖集合 acquiredLocks 中的所有鎖執行鎖釋放
            if (remainTime <= 0) {
                unlockInner(acquiredLocks);
                // 直接返回false,獲取鎖失敗
                return false;
            }
        }
    }
    if (leaseTime > 0) {
        // 重置鎖過期時間
        acquiredLocks.stream()
                .map(l -> (RedissonBaseLock) l)
                .map(l -> l.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS))
                .forEach(f -> f.toCompletableFuture().join());
    }
    // 如果邏輯正常執行完則認為最終申請鎖成功,返回true
    return true;
}

從源碼中可以看到,紅鎖的加鎖,其實就是循環所有加鎖的節點,挨個執行LUA腳本加鎖,對于加鎖成功的那些節點,會加入到acquiredLocks集合中保存起來;如果加鎖失敗的話,則會判斷已經申請鎖失敗的節點是否已經到達允許加鎖失敗節點個數限制 (N-(N/2+1)), 如果已經到達,就認定最終申請鎖失敗,則沒有必要繼續從后面的節點申請了。

并且,每個節點執行完tryLock()嘗試獲取鎖之后,無論是否獲取鎖成功,都會判斷目前從各個節點獲取鎖已經消耗的總時間,如果已經等于最大等待時間,則認定最終申請鎖失敗,需要對成功加鎖集合 acquiredLocks 中的所有鎖執行鎖釋放,然后返回false。

五、RedLock算法問題

1、持久化問題

假設一共有5個Redis節點:A, B, C, D, E:

客戶端1成功鎖住了A, B, C,獲取鎖成功,但D和E沒有鎖住。

節點C崩潰重啟了,但客戶端1在C上加的鎖沒有持久化下來,丟失了。

節點C重啟后,客戶端2鎖住了C, D, E,獲取鎖成功。

這樣,客戶端1和客戶端2同時獲得了鎖(針對同一資源)。

2、客戶端長時間阻塞,導致獲得的鎖釋放,訪問的共享資源不受保護的問題。

3、Redlock算法對時鐘依賴性太強, 若某個節點中發生時間跳躍(系統時間戳不正確),也可能會引此而引發鎖安全性問題。

“Redisson RedLock紅鎖加鎖實現過程及原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

张家港市| 阿坝| 榆社县| 澄江县| 镇原县| 时尚| 黄骅市| 金坛市| 涞源县| 东乌| 巫溪县| 凤凰县| 兰溪市| 腾冲县| 望都县| 平阴县| 华宁县| 区。| 如皋市| 临桂县| 新营市| 蕉岭县| 基隆市| 石台县| 松滋市| 弋阳县| 临武县| 大新县| 东山县| 秭归县| 遂昌县| 潢川县| 宜阳县| 郓城县| 德兴市| 虹口区| 麦盖提县| 洛川县| 达孜县| 奉新县| 高雄县|