1. 程式人生 > 程式設計 >分散式鎖的由來、特點、及Redis分散式鎖的實現詳解

分散式鎖的由來、特點、及Redis分散式鎖的實現詳解

什麼是分散式鎖

要介紹分散式鎖,首先要提到與分散式鎖相對應的是執行緒鎖、程式鎖。

1.執行緒鎖

主要用來給方法、程式碼塊加鎖。當某個方法或程式碼使用鎖,在同一時刻僅有一個執行緒執行該方法或該程式碼段。執行緒鎖只在同一JVM中有效果,因為執行緒鎖的實現在根本上是依靠執行緒之間共享記憶體實現的,比如Synchronized、Lock等。

2.程式鎖

為了控制同一作業系統中多個程式訪問某個共享資源,因為程式具有獨立性,各個程式無法訪問其他程式的資源,因此無法通過synchronized等執行緒鎖實現程式鎖。

3.分散式鎖

當多個程式不在同一個系統中,用分散式鎖控制多個程式對資源的訪問。

分散式鎖的由來

在傳統單機部署的情況下,可以使用Java併發處理相關的API(如ReentrantLcok或synchronized)進行互斥控制。

但是在分散式系統後,由於分散式系統多執行緒、多程式並且分佈在不同機器上,這將使原單機併發控制鎖策略失效,為瞭解決這個問題就需要一種跨JVM的互斥機制來控制共享資源的訪問,這就是分散式鎖的由來。

當多個程式不在同一個系統中,就需要用分散式鎖控制多個程式對資源的訪問。

分散式鎖的特點首先,為了確保分散式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件:

1、互斥性:任意時刻,只能有一個客戶端獲取鎖,不能同時有兩個客戶端獲取到鎖。

2、安全性:鎖只能被持有該鎖的客戶端刪除,不能由其它客戶端刪除。

3、死鎖:獲取鎖的客戶端因為某些原因(如down機等)而未能釋放鎖,其它客戶端再也無法獲取到該鎖。

4、容錯:當部分節點(redis節點等)down機時,客戶端仍然能夠獲取鎖和釋放鎖。

分散式鎖的具體實現

高併發架構系列:什麼是分散式鎖?Redis實現分散式鎖詳解

分散式鎖一般有三種實現方式:

  1. 資料庫樂觀鎖;
  2. 基於ZooKeeper的分散式鎖;
  3. 基於Redis的分散式鎖;

Redis實現分散式鎖

基於Redis命令:SET key value NX EX max-lock-time

這裡補充下: 從2.6.12版本後,就可以使用set來獲取鎖,Lua 指令碼來釋放鎖。setnx是老黃曆了,set命令nx,xx等引數,是為了實現 setnx 的功能。

1.加鎖

public class RedisTool {

private static final String LOCK_SUCCESS = “OK”;

private static final String SETIFNOT_EXIST = “NX”;

private static final String SETWITHEXPIRE_TIME = “PX”;

/**

  • 嘗試獲取分散式鎖
  • @param jedis Redis客戶端
  • @param lockKey 鎖
  • @param requestId 請求標識
  • @param expireTime 超期時間
  • @return 是否獲取成功
    */

public static boolean tryGetDistributedLock(Jedis jedis,String lockKey,String requestId,int expireTime) {

String result = jedis.set(lockKey,requestId,SETIFNOTEXIST,SETWITHEXPIRETIME,expireTime);if (LOCK_SUCCESS.equals(result)) {return true;}return false;}

}

jedis.set(String key,String value,String nxxx,String expx,int time)

這個set()方法一共有五個形參:

第一個為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. 已有鎖存在,不做任何操作。

2.解鎖

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 requestId) {

String script = “if redis.call(‘get’,KEYS[1]) == ARGV[1] then return redis.call(‘del’,KEYS[1]) else return 0 end”;Objectresult = jedis.eval(script,Collections.singletonList(lockKey),Collections.singletonList(requestId));if(RELEASE_SUCCESS.equals(result)) {return true;}return false;}

}

那麼這段Lua程式碼的功能是什麼呢?其實很簡單,首先獲取鎖對應的value值,檢查是否與requestId相等,如果相等則刪除鎖(解鎖)。

file

歡迎新增小編的Java學習群,不管你是小白還是大牛,小編我都歡迎,不定期分享乾貨,包括小編自己整理的一份2019年最新的Java資料和0基礎入門教程視訊,歡迎初學和進階中的小夥伴。在不忙的時間我會給大家解惑

本文由部落格一文多發平臺 OpenWrite 釋出!