'Redis分佈式鎖最牛逼的實現'

Redis 算法 Lua UNIX 程序員聖經 2019-08-18
"
來源:公眾號 阿飛的博客 ,
作者 阿飛的博客

普通實現

說道Redis分佈式鎖大部分人都會想到:setnx+lua,或者知道set key value px milliseconds nx。後一種方式的核心實現命令如下:

"
來源:公眾號 阿飛的博客 ,
作者 阿飛的博客

普通實現

說道Redis分佈式鎖大部分人都會想到:setnx+lua,或者知道set key value px milliseconds nx。後一種方式的核心實現命令如下:

Redis分佈式鎖最牛逼的實現

這種實現方式有3大要點(也是面試概率非常高的地方):

  1. set命令要用set key value px milliseconds nx;
  2. value要具有唯一性;
  3. 釋放鎖時要驗證value值,不能誤解鎖;

事實上這類瑣最大的缺點就是它加鎖時只作用在一個Redis節點上,即使Redis通過sentinel保證高可用,如果這個master節點由於某些原因發生了主從切換,那麼就會出現鎖丟失的情況:

  1. 在Redis的master節點上拿到了鎖;
  2. 但是這個加鎖的key還沒有同步到slave節點;
  3. master故障,發生故障轉移,slave節點升級為master節點;
  4. 導致鎖丟失。

正因為如此,Redis作者antirez基於分佈式環境下提出了一種更高級的分佈式鎖的實現方式:Redlock。筆者認為,Redlock也是Redis所有分佈式鎖實現方式中唯一能讓面試官高潮的方式。

Redlock實現

antirez提出的redlock算法大概是這樣的:

在Redis的分佈式環境中,我們假設有N個Redis master。這些節點完全互相獨立,不存在主從複製或者其他集群協調機制。我們確保將在N個實例上使用與在Redis單實例下相同方法獲取和釋放鎖。現在我們假設有5個Redis master節點,同時我們需要在5臺服務器上面運行這些Redis實例,這樣保證他們不會同時都宕掉。

為了取到鎖,客戶端應該執行以下操作:

  • 獲取當前Unix時間,以毫秒為單位。
  • 依次嘗試從5個實例,使用相同的key和具有唯一性的value(例如UUID)獲取鎖。當向Redis請求獲取鎖時,客戶端應該設置一個網絡連接和響應超時時間,這個超時時間應該小於鎖的失效時間。例如你的鎖自動失效時間為10秒,則超時時間應該在5-50毫秒之間。這樣可以避免服務器端Redis已經掛掉的情況下,客戶端還在死死地等待響應結果。如果服務器端沒有在規定時間內響應,客戶端應該儘快嘗試去另外一個Redis實例請求獲取鎖。
  • 客戶端使用當前時間減去開始獲取鎖時間(步驟1記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數(N/2+1,這裡是3個節點)的Redis節點都取到鎖,並且使用的時間小於鎖失效時間時,鎖才算獲取成功
  • 如果取到了鎖,key的真正有效時間等於有效時間減去獲取鎖所使用的時間(步驟3計算的結果)。
  • 如果因為某些原因,獲取鎖失敗(沒有在至少N/2+1個Redis實例取到鎖或者取鎖時間已經超過了有效時間),客戶端應該在所有的Redis實例上進行解鎖(即便某些Redis實例根本就沒有加鎖成功,防止某些節點獲取到鎖但是客戶端沒有得到響應而導致接下來的一段時間不能被重新獲取鎖)。

Redlock源碼

redisson已經有對redlock算法封裝,接下來對其用法進行簡單介紹,並對核心源碼進行分析(假設5個redis實例)。

  • POM依賴
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.3.2</version>
</dependency>

用法

首先,我們來看一下redission封裝的redlock算法實現的分佈式鎖用法,非常簡單,跟重入鎖(ReentrantLock)有點類似:

"
來源:公眾號 阿飛的博客 ,
作者 阿飛的博客

普通實現

說道Redis分佈式鎖大部分人都會想到:setnx+lua,或者知道set key value px milliseconds nx。後一種方式的核心實現命令如下:

Redis分佈式鎖最牛逼的實現

這種實現方式有3大要點(也是面試概率非常高的地方):

  1. set命令要用set key value px milliseconds nx;
  2. value要具有唯一性;
  3. 釋放鎖時要驗證value值,不能誤解鎖;

事實上這類瑣最大的缺點就是它加鎖時只作用在一個Redis節點上,即使Redis通過sentinel保證高可用,如果這個master節點由於某些原因發生了主從切換,那麼就會出現鎖丟失的情況:

  1. 在Redis的master節點上拿到了鎖;
  2. 但是這個加鎖的key還沒有同步到slave節點;
  3. master故障,發生故障轉移,slave節點升級為master節點;
  4. 導致鎖丟失。

正因為如此,Redis作者antirez基於分佈式環境下提出了一種更高級的分佈式鎖的實現方式:Redlock。筆者認為,Redlock也是Redis所有分佈式鎖實現方式中唯一能讓面試官高潮的方式。

Redlock實現

antirez提出的redlock算法大概是這樣的:

在Redis的分佈式環境中,我們假設有N個Redis master。這些節點完全互相獨立,不存在主從複製或者其他集群協調機制。我們確保將在N個實例上使用與在Redis單實例下相同方法獲取和釋放鎖。現在我們假設有5個Redis master節點,同時我們需要在5臺服務器上面運行這些Redis實例,這樣保證他們不會同時都宕掉。

為了取到鎖,客戶端應該執行以下操作:

  • 獲取當前Unix時間,以毫秒為單位。
  • 依次嘗試從5個實例,使用相同的key和具有唯一性的value(例如UUID)獲取鎖。當向Redis請求獲取鎖時,客戶端應該設置一個網絡連接和響應超時時間,這個超時時間應該小於鎖的失效時間。例如你的鎖自動失效時間為10秒,則超時時間應該在5-50毫秒之間。這樣可以避免服務器端Redis已經掛掉的情況下,客戶端還在死死地等待響應結果。如果服務器端沒有在規定時間內響應,客戶端應該儘快嘗試去另外一個Redis實例請求獲取鎖。
  • 客戶端使用當前時間減去開始獲取鎖時間(步驟1記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數(N/2+1,這裡是3個節點)的Redis節點都取到鎖,並且使用的時間小於鎖失效時間時,鎖才算獲取成功
  • 如果取到了鎖,key的真正有效時間等於有效時間減去獲取鎖所使用的時間(步驟3計算的結果)。
  • 如果因為某些原因,獲取鎖失敗(沒有在至少N/2+1個Redis實例取到鎖或者取鎖時間已經超過了有效時間),客戶端應該在所有的Redis實例上進行解鎖(即便某些Redis實例根本就沒有加鎖成功,防止某些節點獲取到鎖但是客戶端沒有得到響應而導致接下來的一段時間不能被重新獲取鎖)。

Redlock源碼

redisson已經有對redlock算法封裝,接下來對其用法進行簡單介紹,並對核心源碼進行分析(假設5個redis實例)。

  • POM依賴
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.3.2</version>
</dependency>

用法

首先,我們來看一下redission封裝的redlock算法實現的分佈式鎖用法,非常簡單,跟重入鎖(ReentrantLock)有點類似:

Redis分佈式鎖最牛逼的實現

唯一ID

實現分佈式鎖的一個非常重要的點就是set的value要具有唯一性,redisson的value是怎樣保證value的唯一性呢?答案是UUID+threadId。入口在redissonClient.getLock("REDLOCK_KEY"),源碼在Redisson.java和RedissonLock.java中:

protected final UUID id = UUID.randomUUID();
String getLockName(long threadId) {
return id + ":" + threadId;
}

獲取鎖

獲取鎖的代碼為redLock.tryLock()或者redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS),兩者的最終核心源碼都是下面這段代碼,只不過前者獲取鎖的默認租約時間(leaseTime)是LOCK_EXPIRATION_INTERVAL_SECONDS,即30s:

"
來源:公眾號 阿飛的博客 ,
作者 阿飛的博客

普通實現

說道Redis分佈式鎖大部分人都會想到:setnx+lua,或者知道set key value px milliseconds nx。後一種方式的核心實現命令如下:

Redis分佈式鎖最牛逼的實現

這種實現方式有3大要點(也是面試概率非常高的地方):

  1. set命令要用set key value px milliseconds nx;
  2. value要具有唯一性;
  3. 釋放鎖時要驗證value值,不能誤解鎖;

事實上這類瑣最大的缺點就是它加鎖時只作用在一個Redis節點上,即使Redis通過sentinel保證高可用,如果這個master節點由於某些原因發生了主從切換,那麼就會出現鎖丟失的情況:

  1. 在Redis的master節點上拿到了鎖;
  2. 但是這個加鎖的key還沒有同步到slave節點;
  3. master故障,發生故障轉移,slave節點升級為master節點;
  4. 導致鎖丟失。

正因為如此,Redis作者antirez基於分佈式環境下提出了一種更高級的分佈式鎖的實現方式:Redlock。筆者認為,Redlock也是Redis所有分佈式鎖實現方式中唯一能讓面試官高潮的方式。

Redlock實現

antirez提出的redlock算法大概是這樣的:

在Redis的分佈式環境中,我們假設有N個Redis master。這些節點完全互相獨立,不存在主從複製或者其他集群協調機制。我們確保將在N個實例上使用與在Redis單實例下相同方法獲取和釋放鎖。現在我們假設有5個Redis master節點,同時我們需要在5臺服務器上面運行這些Redis實例,這樣保證他們不會同時都宕掉。

為了取到鎖,客戶端應該執行以下操作:

  • 獲取當前Unix時間,以毫秒為單位。
  • 依次嘗試從5個實例,使用相同的key和具有唯一性的value(例如UUID)獲取鎖。當向Redis請求獲取鎖時,客戶端應該設置一個網絡連接和響應超時時間,這個超時時間應該小於鎖的失效時間。例如你的鎖自動失效時間為10秒,則超時時間應該在5-50毫秒之間。這樣可以避免服務器端Redis已經掛掉的情況下,客戶端還在死死地等待響應結果。如果服務器端沒有在規定時間內響應,客戶端應該儘快嘗試去另外一個Redis實例請求獲取鎖。
  • 客戶端使用當前時間減去開始獲取鎖時間(步驟1記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數(N/2+1,這裡是3個節點)的Redis節點都取到鎖,並且使用的時間小於鎖失效時間時,鎖才算獲取成功
  • 如果取到了鎖,key的真正有效時間等於有效時間減去獲取鎖所使用的時間(步驟3計算的結果)。
  • 如果因為某些原因,獲取鎖失敗(沒有在至少N/2+1個Redis實例取到鎖或者取鎖時間已經超過了有效時間),客戶端應該在所有的Redis實例上進行解鎖(即便某些Redis實例根本就沒有加鎖成功,防止某些節點獲取到鎖但是客戶端沒有得到響應而導致接下來的一段時間不能被重新獲取鎖)。

Redlock源碼

redisson已經有對redlock算法封裝,接下來對其用法進行簡單介紹,並對核心源碼進行分析(假設5個redis實例)。

  • POM依賴
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.3.2</version>
</dependency>

用法

首先,我們來看一下redission封裝的redlock算法實現的分佈式鎖用法,非常簡單,跟重入鎖(ReentrantLock)有點類似:

Redis分佈式鎖最牛逼的實現

唯一ID

實現分佈式鎖的一個非常重要的點就是set的value要具有唯一性,redisson的value是怎樣保證value的唯一性呢?答案是UUID+threadId。入口在redissonClient.getLock("REDLOCK_KEY"),源碼在Redisson.java和RedissonLock.java中:

protected final UUID id = UUID.randomUUID();
String getLockName(long threadId) {
return id + ":" + threadId;
}

獲取鎖

獲取鎖的代碼為redLock.tryLock()或者redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS),兩者的最終核心源碼都是下面這段代碼,只不過前者獲取鎖的默認租約時間(leaseTime)是LOCK_EXPIRATION_INTERVAL_SECONDS,即30s:

Redis分佈式鎖最牛逼的實現

獲取鎖的命令中,

  • KEYS[1]就是Collections.singletonList(getName()),表示分佈式鎖的key,即REDLOCK_KEY;
  • ARGV[1]就是internalLockLeaseTime,即鎖的租約時間,默認30s;
  • ARGV[2]就是getLockName(threadId),是獲取鎖時set的唯一值,即UUID+threadId:

釋放鎖

釋放鎖的代碼為redLock.unlock(),核心源碼如下:

"
來源:公眾號 阿飛的博客 ,
作者 阿飛的博客

普通實現

說道Redis分佈式鎖大部分人都會想到:setnx+lua,或者知道set key value px milliseconds nx。後一種方式的核心實現命令如下:

Redis分佈式鎖最牛逼的實現

這種實現方式有3大要點(也是面試概率非常高的地方):

  1. set命令要用set key value px milliseconds nx;
  2. value要具有唯一性;
  3. 釋放鎖時要驗證value值,不能誤解鎖;

事實上這類瑣最大的缺點就是它加鎖時只作用在一個Redis節點上,即使Redis通過sentinel保證高可用,如果這個master節點由於某些原因發生了主從切換,那麼就會出現鎖丟失的情況:

  1. 在Redis的master節點上拿到了鎖;
  2. 但是這個加鎖的key還沒有同步到slave節點;
  3. master故障,發生故障轉移,slave節點升級為master節點;
  4. 導致鎖丟失。

正因為如此,Redis作者antirez基於分佈式環境下提出了一種更高級的分佈式鎖的實現方式:Redlock。筆者認為,Redlock也是Redis所有分佈式鎖實現方式中唯一能讓面試官高潮的方式。

Redlock實現

antirez提出的redlock算法大概是這樣的:

在Redis的分佈式環境中,我們假設有N個Redis master。這些節點完全互相獨立,不存在主從複製或者其他集群協調機制。我們確保將在N個實例上使用與在Redis單實例下相同方法獲取和釋放鎖。現在我們假設有5個Redis master節點,同時我們需要在5臺服務器上面運行這些Redis實例,這樣保證他們不會同時都宕掉。

為了取到鎖,客戶端應該執行以下操作:

  • 獲取當前Unix時間,以毫秒為單位。
  • 依次嘗試從5個實例,使用相同的key和具有唯一性的value(例如UUID)獲取鎖。當向Redis請求獲取鎖時,客戶端應該設置一個網絡連接和響應超時時間,這個超時時間應該小於鎖的失效時間。例如你的鎖自動失效時間為10秒,則超時時間應該在5-50毫秒之間。這樣可以避免服務器端Redis已經掛掉的情況下,客戶端還在死死地等待響應結果。如果服務器端沒有在規定時間內響應,客戶端應該儘快嘗試去另外一個Redis實例請求獲取鎖。
  • 客戶端使用當前時間減去開始獲取鎖時間(步驟1記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數(N/2+1,這裡是3個節點)的Redis節點都取到鎖,並且使用的時間小於鎖失效時間時,鎖才算獲取成功
  • 如果取到了鎖,key的真正有效時間等於有效時間減去獲取鎖所使用的時間(步驟3計算的結果)。
  • 如果因為某些原因,獲取鎖失敗(沒有在至少N/2+1個Redis實例取到鎖或者取鎖時間已經超過了有效時間),客戶端應該在所有的Redis實例上進行解鎖(即便某些Redis實例根本就沒有加鎖成功,防止某些節點獲取到鎖但是客戶端沒有得到響應而導致接下來的一段時間不能被重新獲取鎖)。

Redlock源碼

redisson已經有對redlock算法封裝,接下來對其用法進行簡單介紹,並對核心源碼進行分析(假設5個redis實例)。

  • POM依賴
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.3.2</version>
</dependency>

用法

首先,我們來看一下redission封裝的redlock算法實現的分佈式鎖用法,非常簡單,跟重入鎖(ReentrantLock)有點類似:

Redis分佈式鎖最牛逼的實現

唯一ID

實現分佈式鎖的一個非常重要的點就是set的value要具有唯一性,redisson的value是怎樣保證value的唯一性呢?答案是UUID+threadId。入口在redissonClient.getLock("REDLOCK_KEY"),源碼在Redisson.java和RedissonLock.java中:

protected final UUID id = UUID.randomUUID();
String getLockName(long threadId) {
return id + ":" + threadId;
}

獲取鎖

獲取鎖的代碼為redLock.tryLock()或者redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS),兩者的最終核心源碼都是下面這段代碼,只不過前者獲取鎖的默認租約時間(leaseTime)是LOCK_EXPIRATION_INTERVAL_SECONDS,即30s:

Redis分佈式鎖最牛逼的實現

獲取鎖的命令中,

  • KEYS[1]就是Collections.singletonList(getName()),表示分佈式鎖的key,即REDLOCK_KEY;
  • ARGV[1]就是internalLockLeaseTime,即鎖的租約時間,默認30s;
  • ARGV[2]就是getLockName(threadId),是獲取鎖時set的唯一值,即UUID+threadId:

釋放鎖

釋放鎖的代碼為redLock.unlock(),核心源碼如下:

Redis分佈式鎖最牛逼的實現

參考:https://redis.io/topics/distlock

"

相關推薦

推薦中...