redis分佈式鎖

可靠性

首先,爲了確保分佈式鎖可用,咱們至少要確保鎖的實現同時知足如下四個條件:redis

  1. 互斥性。在任意時刻,只有一個客戶端能持有鎖。
  2. 不會發生死鎖。即便有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其餘客戶端能加鎖。
  3. 具備容錯性。只要大部分的Redis節點正常運行,客戶端就能夠加鎖和解鎖。
  4. 解鈴還須繫鈴人。加鎖和解鎖必須是同一個客戶端,客戶端本身不能把別人加的鎖給解了。

上鎖:dom

ublic class RedisTool {分佈式

private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";函數

/**
* 嘗試獲取分佈式鎖
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請求標識
* @param expireTime 超期時間
* @return 是否獲取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {spa

String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);code

if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;ip

}部署

}get

能夠看到,咱們加鎖就一行代碼:jedis.set(String key, String value, String nxxx, String expx, int time),這個set()方法一共有五個形參:io

  • 第一個爲key,咱們使用key來當鎖,由於key是惟一的。

  • 第二個爲value,咱們傳的是requestId,不少童鞋可能不明白,有key做爲鎖不就夠了嗎,爲何還要用到value?緣由就是咱們在上面講到可靠性時,分佈式鎖要知足第四個條件解鈴還須繫鈴人,經過給value賦值爲requestId,咱們就知道這把鎖是哪一個請求加的了,在解鎖的時候就能夠有依據。requestId能夠使用UUID.randomUUID().toString()方法生成。

  • 第三個爲nxxx,這個參數咱們填的是NX,意思是SET IF NOT EXIST,即當key不存在時,咱們進行set操做;若key已經存在,則不作任何操做;

  • 第四個爲expx,這個參數咱們傳的是PX,意思是咱們要給這個key加一個過時的設置,具體時間由第五個參數決定。

  • 第五個爲time,與第四個參數相呼應,表明key的過時時間。

總的來講,執行上面的set()方法就只會致使兩種結果:1. 當前沒有鎖(key不存在),那麼就進行加鎖操做,並對鎖設置個有效期,同時value表示加鎖的客戶端。2. 已有鎖存在,不作任何操做。

心細的童鞋就會發現了,咱們的加鎖代碼知足咱們可靠性裏描述的三個條件。首先,set()加入了NX參數,能夠保證若是已有key存在,則函數不會調用成功,也就是隻有一個客戶端能持有鎖,知足互斥性。其次,因爲咱們對鎖設置了過時時間,即便鎖的持有者後續發生崩潰而沒有解鎖,鎖也會由於到了過時時間而自動解鎖(即key被刪除),不會發生死鎖。最後,由於咱們將value賦值爲requestId,表明加鎖的客戶端請求標識,那麼在客戶端在解鎖的時候就能夠進行校驗是不是同一個客戶端。因爲咱們只考慮Redis單機部署的場景,因此容錯性咱們暫不考慮。

解鎖:

public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 釋放分佈式鎖
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param requestId 請求標識
     * @return 是否釋放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}
那麼這段Lua代碼的功能是什麼呢?其實很簡單,首先獲取鎖對應的value值,檢查是否與requestId相等,若是相等則刪除鎖(解鎖)。
那麼爲何要使用Lua語言來實現呢?由於要確保上述操做是原子性的。關於非原子性會帶來什麼問題,
能夠閱讀【解鎖代碼-錯誤示例2】 。那麼爲何執行eval()方法能夠確保原子性,源於Redis的特性,下面是官網對eval命令的部分解釋:

集羣環境中使用Redis實現分佈式鎖兩種方式

相關文章
相關標籤/搜索