分佈式紅鎖的加鎖失敗的設計原理

分佈式紅鎖的加鎖失敗的設計原理

1.先把3臺 redis key全部清空(爲了不受debug干擾,必須先刪除鎖)
127.0.0.1:6379> flushdb
OK

都設置爲30分鐘超時 過期
2.isLock = redLock.tryLock(10006030, 10006030, TimeUnit.MILLISECONDS);

試驗步驟:

  1. 先啓動3臺redis實例,然後啓動springboot
  2. springboot啓動成功後,停2臺redis實例。(不能先停2臺redis,不然springboot起不來)

步驟1:springboot啓動成功後,停2臺redis實例。

[root@node2 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
1e6e3900e68c        redis:5.0.7         "docker-entrypoint.s…"   6 days ago          Up 4 minutes        0.0.0.0:6383->6379/tcp   redis-master-3
1b9030d50927        redis:5.0.7         "docker-entrypoint.s…"   6 days ago          Up 4 minutes        0.0.0.0:6382->6379/tcp   redis-master-2
c86403dcb3d8        redis:5.0.7         "docker-entrypoint.s…"   6 days ago          Up 8 hours          0.0.0.0:6381->6379/tcp   redis-master-1
[root@node2 ~]#
[root@node2 ~]#
[root@node2 ~]# docker stop redis-master-3 redis-master-2
redis-master-3
redis-master-2
    
    @Override
    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 != -1) {
            if (waitTime == -1) {
                newLeaseTime = unit.toMillis(leaseTime);
            } else {
                newLeaseTime = unit.toMillis(waitTime)*2;
            }
        }
        
        long time = System.currentTimeMillis();
        long remainTime = -1;
        if (waitTime != -1) {
            remainTime = unit.toMillis(waitTime);
        }
        long lockWaitTime = calcLockWaitTime(remainTime);
        
        //步驟2:計算可以容忍接受加鎖失敗節點個數限制(N-(N/2+1))=(3-(3/2+1))=1
        int failedLocksLimit = failedLocksLimit(); ==1
        List<RLock> acquiredLocks = new ArrayList<>(locks.size());
        for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
            RLock lock = iterator.next();
            boolean lockAcquired;
            try {
                if (waitTime == -1 && leaseTime == -1) {
                    lockAcquired = lock.tryLock();
                } else {
                    long awaitTime = Math.min(lockWaitTime, remainTime);
                    lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
                }
            } catch (RedisResponseTimeoutException e) {
                unlockInner(Arrays.asList(lock));
                lockAcquired = false;
            } catch (Exception e) {
                lockAcquired = false;
            }
            //步驟3:拿鎖成功後,統計成功的redis實例數
            if (lockAcquired) {
                acquiredLocks.add(lock);
            } else {//步驟4:拿鎖失敗的話,那就複雜了
            計算可以容忍接受加鎖失敗節點數,是否達到?(N-(N/2+1))=(3-(3/2+1))=1
            如果已經達到,就認定最終申請鎖失敗,則沒有必要繼續從後面的節點申請了
            因爲紅鎖算法要求至少N/2+1=3/2+1=2個節點都加鎖成功了,纔算最終的鎖申請成功。
            
                //剛好N/2+1=3/2+1=2是成功的,例如 3個或2個都是成功,就退出,代表獲取鎖成功。
                if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
                    break;
                }
                //步驟B:例如redis3個節點,第一個成功,第二失敗,第三失敗,,就直接進入failedLocksLimit=0
                if (failedLocksLimit == 0) {
                    //解鎖第一個成功的
                    unlockInner(acquiredLocks);
                    //等待時間已經消化完就,就直接返回false,獲取鎖失敗。
                    if (waitTime == -1) {
                        return false;
                    }
                    //計算可以容忍接受加鎖失敗節點個數限制(N-(N/2+1))=(3-(3/2+1))=1
                    failedLocksLimit = failedLocksLimit();
                    //清除成功的list,例如redis3個節點,第一個成功,第二失敗,第三失敗,就是吧第一個清除,然後重新來。
                    acquiredLocks.clear();
                    //把iterator的座標重新初始化,然後重新進入新的循環
                    // reset iterator
                    while (iterator.hasPrevious()) {
                        iterator.previous();
                    }
                    
                    //總結:failedLocksLimit == 0的設計原理,就是讓for循環一直循環下去,除非出現2種情況才退出死循環
                    1.waitTime 或 remainTime消化完,退出循環
                    2.redis實例恢復正常  (例如啓動  docker start redis-master-3 redis-master-2)就正常拿到鎖了
                    
                    
                    
                } else {
                    //步驟A:加鎖失敗節點個數限制-1,然後繼續循環,例如redis3個節點,第一個成功,第二失敗,限制failedLocksLimit=0
                    failedLocksLimit--;
                }
            }
            //步驟5: 只有等到remainTime消化完,退出死循環
            if (remainTime != -1) {
                remainTime -= System.currentTimeMillis() - time;
                time = System.currentTimeMillis();
                if (remainTime <= 0) {
                    unlockInner(acquiredLocks);
                    return false;
                }
            }
        }
        
        if (leaseTime != -1) {
            List<RFuture<Boolean>> futures = new ArrayList<>(acquiredLocks.size());
            for (RLock rLock : acquiredLocks) {
                RFuture<Boolean> future = ((RedissonLock) rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);
                futures.add(future);
            }
            
            for (RFuture<Boolean> rFuture : futures) {
                rFuture.syncUninterruptibly();
            }
        }
        
        return true;
    }

基於以上代碼,我們分爲5個步驟分析,得出一個結論:
1.整個算法過程中都是圍繞N/2+1=3/2+1=2的成功個數來設計的,如果拿不到N/2+1=3/2+1=2,算法會一直死循環下去,,除非出現2種情況才退出死循環
1.waitTime 或 remainTime消化完,退出循環
2.redis實例恢復正常 (例如啓動 docker start redis-master-3 redis-master-2)就正常拿到鎖了
2.這種設計,要特別小心waitTime,這個waitTime一定要設置短,不然redis宕機的情況下,會出現死循環,整個系統卡死。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章