基於Redis實現簡單的分佈式鎖

摘要

分佈式鎖在不少應用場景下是很是有效的手段,好比當運行在多個機器上的不一樣進程須要訪問同一個競爭資源的時候,那麼就會涉及到進程對資源的加鎖和釋放,這樣才能保證數據的安全訪問。分佈式鎖實現的方案有不少,好比基於ZooKeeper實現、或者基於Mysql實現等等,今天咱們來一塊兒看看如何基於Redis實現分佈式鎖服務。redis

 

分佈式鎖要點

對於分佈式鎖的目標,咱們必須首先明確三點:算法

  1. 任何一個時間點必須只可以有一個客戶端擁有鎖。
  2. 不可以有死鎖,也就是最終客戶端都可以得到鎖,儘管可能會經歷失敗。
  3. 錯誤容忍性要好,只要有大部分的Redis實例存活,客戶端就應該可以得到鎖。

 

一種簡單的方法

理解了上面咱們列出的三個點,咱們來分析一下通常的基於Redis實現的分佈式鎖:sql

使用Redis實現鎖最簡單的辦法是建立一個key,且這個key一般有有限的存活時間,這一點能夠利用Redis的過時時間特性,因此鎖最終會被釋放掉,當客戶端須要釋放資源的時候,客戶端delete這個key便可。安全

So far so good!可是有個單點問題,假如Redis master掛掉怎麼辦,所以咱們須要加個slave,當master掛掉的時候能夠切換到slave。這又帶來了新的問題,因爲Redis的複製是異步的,所以咱們不能保證同時只有一個客戶端得到鎖。dom

這個模型有很顯然的競態:異步

  1. Client在Master上面得到了鎖。
  2. master在數據同步到slave以前掛掉了。
  3. slave升級成爲master。
  4. Client B申請了一樣的資源的鎖,成功了!

在特定條件下這種狀況是會發生的,當出現多個客戶端同時得到鎖的時候,咱們就認爲能夠這種鎖方案是不可靠的。分佈式

 

基於Redis單例的實現

爲了後面更好的瞭解分佈式鎖的實現,咱們先來看看如何基於Redis單例實現鎖服務。咱們能夠用下面方法得到鎖:spa

SET resource_name my_random_value NX PX 30000

上面的命令在只有當key不存在的時候會執行成功(NX選項),同時會設置過時時間爲30000ms(PX選項)。key的值會被設置爲my_random_value。這個值在多個客戶端和鎖中必須是惟一的,咱們使用random value是爲了方便安全地釋放鎖,看看下面的腳本:code

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

只有當key存在且值是預期的值的時候纔會刪除key。這種方式能夠避免誤刪除其餘客戶端建立的鎖。例如,當客戶端獲取鎖以後執行一個很長時間的邏輯,一直過了鎖的過時時間,這個時候鎖會被自動釋放掉,而另一個客戶端又獲取了這個鎖,前一個客戶端終於執行完了邏輯執行,回頭釋放鎖,刪除key,其實這個時候釋放的已是另一個客戶端持有的鎖了。使用DEL是不安全的,由於客戶端有可能誤刪其餘客戶端持有的鎖。上面腳本的方法的好處是每次得到鎖的時候加上一個隨機的簽名,當釋放鎖的時候去看看是否是本身持有的鎖,這個時候就不會誤刪。    進程

如今咱們學會了如何在Redis單例上獲取鎖和釋放鎖,那麼接下來咱們看看如何在Redis集羣上獲取鎖和釋放鎖。

 

基於Redlock算法的實現

在分佈式環境下,假設咱們有N個master,這些節點都是獨立的,所以咱們沒有配置複製策略。上面咱們已經學會了如何在單機環境下獲取鎖和釋放鎖,咱們假設的更具體一些,N=5,爲了能獲取鎖,客戶端的步驟爲:

  1. 獲取當前系統的時間,以毫秒爲單位。
  2. 順序的獲取N個Redis實例上的鎖,在每一個實例中都用一樣的key和value。在步驟2中,客戶端須要一個比過時時間小不少的超時時間,例如,若是自動過時時間爲10s,那麼超時時間大概是5~50ms,這樣能夠避免客戶端一直被阻塞,而不能繼續請求下一個實例。
  3. 客戶端每次都要計算已通過去了多長時間,使用的時間是否小於key自動過時的時間同時又獲取了至少3個實例的鎖。若是是,那麼咱們認爲客戶端這次獲取鎖成功。
  4. 若是鎖被獲取了,鎖的過時時間必需要減去獲取鎖花費的時間。
  5. 若是當前客戶端獲取鎖失敗,客戶端須要釋放全部以前獲取到的鎖。

 

總結

這篇文章主要介紹Redis實現分佈式鎖的基本方法,而後分別介紹經過Redis單例和Redis集羣實現分佈式鎖的方法。

 

參考文獻

《Redis官方文檔》

相關文章
相關標籤/搜索