分佈式鎖的由來、特色、及Redis分佈式鎖的實現詳解

什麼是分佈式鎖

要介紹分佈式鎖,首先要提到與分佈式鎖相對應的是線程鎖、進程鎖。redis

1.線程鎖

主要用來給方法、代碼塊加鎖。當某個方法或代碼使用鎖,在同一時刻僅有一個線程執行該方法或該代碼段。線程鎖只在同一JVM中有效果,由於線程鎖的實如今根本上是依靠線程之間共享內存實現的,好比Synchronized、Lock等。數據庫

2.進程鎖

爲了控制同一操做系統中多個進程訪問某個共享資源,由於進程具備獨立性,各個進程沒法訪問其餘進程的資源,所以沒法經過synchronized等線程鎖實現進程鎖。安全

3.分佈式鎖

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

分佈式鎖的由來

分佈式鎖.jpg

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

可是在分佈式系統後,因爲分佈式系統多線程、多進程而且分佈在不一樣機器上,這將使原單機併發控制鎖策略失效,爲了解決這個問題就須要一種跨JVM的互斥機制來控制共享資源的訪問,這就是分佈式鎖的由來。併發

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

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

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

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

三、死鎖:獲取鎖的客戶端由於某些緣由(如down機等)而未能釋放鎖,其它客戶端再也沒法獲取到該鎖。

四、容錯:當部分節點(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 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) {

String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 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 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相等,若是相等則刪除鎖(解鎖)。

相關文章
相關標籤/搜索