基於redis的一種分佈式鎖

前言:本文介紹了一種基於redis的分佈式鎖,利用jedis實現應用(本文應用於多客戶端+一個redis的架構,並未考慮在redis爲主從架構時的狀況)java

文章理論來源部分引自:https://i.cnblogs.com/EditPosts.aspx?opt=1redis

1、基本原理緩存

一、用一個狀態值表示鎖,對鎖的佔用和釋放經過狀態值來標識。架構

二、redis採用單進程單線程模式,採用隊列模式將併發訪問變成串行訪問,多客戶端對Redis的鏈接並不存在競爭關係。併發

2、基本命令app

一、setNX(SET if Not eXists)分佈式

語法:ui

SETNX key value

將 key 的值設爲 value ,當且僅當 key 不存在。spa

若給定的 key 已經存在,則 SETNX 不作任何動做。線程

SETNX 是『SET if Not eXists』(若是不存在,則 SET)的簡寫

返回值:

  設置成功,返回 1 。
  設置失敗,返回 0

二、getSet

GETSET key value

給定 key 的值設爲 value ,並返回 key 的舊值(old value)

  當 key 存在但不是字符串類型時,返回一個錯誤。

返回值:

  返回給定 key 的舊值。
  當 key 沒有舊值時,也便是, key 不存在時,返回 nil 。

三、get

GET key

  當 key 不存在時,返回 nil ,不然,返回 key 的值。
  若是 key 不是字符串類型,那麼返回一個錯誤

3、取鎖、解鎖以及示例代碼:

    /**
     * @Description:分佈式鎖,經過控制redis中key的過時時間來控制鎖資源的分配
     * 實現思路: 主要是使用了redis 的setnx命令,緩存了鎖.
     * reids緩存的key是鎖的key,全部的共享, value是鎖的到期時間(注意:這裏把過時時間放在value了,沒有時間上設置其超時時間)
     * 執行過程:
     * 1.經過setnx嘗試設置某個key的值,成功(當前沒有這個鎖)則返回,成功得到鎖
     * 2.鎖已經存在則獲取鎖的到期時間,和當前時間比較,超時的話,則設置新的值
     * @param key
     * @param expireTime 有效時間段長度
     * @return
     */
    public boolean getLockKey(String key, final long expireTime) {
        // 1.setnx(lockkey, 當前時間+過時超時時間) ,若是返回1,則獲取鎖成功;若是返回0則沒有獲取到鎖,轉向2
        if (getJedis().setnx(key, new Date().getTime() + expireTime + "") == 1)
            return true;
        String oldExpireTime = getJedis().get(key);
        // 2.get(lockkey)獲取值oldExpireTime
        // ,並將這個value值與當前的系統時間進行比較,若是小於當前系統時間,則認爲這個鎖已經超時,能夠容許別的請求從新獲取,轉向3
        if (null != oldExpireTime && "" !=oldExpireTime  && Long.parseLong(oldExpireTime) < new Date().getTime()) {
            // 3計算newExpireTime=當前時間+過時超時時間,而後getset(lockkey, newExpireTime)
            // 會返回當前lockkey的值currentExpireTime。
            Long newExpireTime = new Date().getTime() + expireTime;
            String currentExpireTime = getJedis().getSet(key, newExpireTime + "");
            // 4.判斷currentExpireTime與oldExpireTime
            // 是否相等,若是相等,說明當前getset設置成功,獲取到了鎖。若是不相等,說明這個鎖又被別的請求獲取走了,
            //那麼當前請求能夠直接返回失敗,或者繼續重試。防止java多個線程進入到該方法形成鎖的獲取混亂。
            if (!currentExpireTime.equals(oldExpireTime)) {
                return false;
            } else {
                return true;
            }
        } else {
            // 鎖被佔用
            return false;
        }
    }
    
    /**
     * 
     * @Description: 若是業務處理完,key的時間還未到期,那麼經過刪除該key來釋放鎖
     * @param key
     * @param dealTime 處理業務的消耗時間
     * @param expireTime 失效時間
     */
    public void deleteLockKey(String key,long dealTime, final long expireTime) {
        if (dealTime < expireTime) {
            getJedis().del(key);
        }
    }

示例:

    // 循環等待獲取鎖
            StringBuilder key = new StringBuilder(KEY_PRE);
            key.append(code).append("_");
            key.append(batchNum);
            long lockTime = 0;
            try {
                while (true) {
                    boolean locked = redisCacheClient.getLockKey(
                            key.toString(), 60000);
                    if (locked) {
                        lockTime = System.currentTimeMillis();
                        break;
                    }
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                
            }
    //業務邏輯...
            
    //業務邏輯進行完,解鎖
            long delLockDateTime =System.currentTimeMillis();
            long dealTime = delLockDateTime - lockTime;
            deleteLockKey(key.toString(), dealTime, 60000);

4、一些問題

一、爲何不直接使用expire設置超時時間,而將時間的毫秒數其做爲value放在redis中?

以下面的方式,把超時的交給redis處理:

lock(key, expireSec){
isSuccess = setnx key
if (isSuccess)
expire key expireSec
}

這種方式貌似沒什麼問題,可是假如在setnx後,redis崩潰了,expire就沒有執行,結果就是死鎖了。鎖永遠不會超時。

二、爲何前面的鎖已經超時了,還要用getSet去設置新的時間戳的時間獲取舊的值,而後和外面的判斷超時時間的時間戳比較呢?

由於是分佈式的環境下,能夠在前一個鎖失效的時候,有兩個進程進入到鎖超時的判斷。如:

C0超時了,還持有鎖,C1/C2同時請求進入了方法裏面

C1/C2獲取到了C0的超時時間

C1使用getSet方法

C2也執行了getSet方法

假如咱們不加 oldValueStr.equals(currentValueStr) 的判斷,將會C1/C2都將得到鎖,加了以後,能保證C1和C2只能一個能得到鎖,一個只能繼續等待。

注意:這裏可能致使超時時間不是其本來的超時時間,C1的超時時間可能被C2覆蓋了,可是他們相差的毫秒及其小,這裏忽略了

 5、不完善之處

一、使用時須要預估業務邏輯處理時間,一旦業務邏輯發生錯誤,那麼只能等到超時以後其餘線程才能拿到鎖,可能會出現問題

相關文章
相關標籤/搜索