Redis++:Redis做分佈式鎖真的靠譜嗎

Redis做分佈式鎖真的靠譜嗎


 

Redis的分佈式鎖可以通過Lua進行實現,通過setnx和expire命令連用的方式 || 也可以使用高版本的方法同時設置失效時間,但是假如在以下情況下,就會造成無鎖的現象。

注:分佈式鎖能不用就不用,尤其是在高併發的情況下。

釋放了不該釋放的鎖:👇

  有時候直接執行 del mylock 是有問題的,我們不能直接執行 del mylock 爲什麼?—— 會導致 “信號錯誤”,釋放了不該釋放的鎖 。

假設如下場景:

時間線線程1線程2線程3
時刻1 執行 setnx mylock val1 加鎖 執行 setnx mylock val2 加鎖 執行 setnx mylock val2 加鎖
時刻2 加鎖成功 加鎖失敗 加鎖失敗
時刻3 執行任務... 嘗試加鎖... 嘗試加鎖...
時刻4 任務繼續(鎖超時,自動釋放了) setnx 獲得了鎖(因爲線程1的鎖超時釋放了) 仍然嘗試加鎖...
時刻5 任務完畢,del mylock 釋放鎖 執行任務中... 獲得了鎖(因爲線程1釋放了線程2的)
...      

上面的表格中,有兩個維度,一個是縱向的時間線,一個是橫線的線程併發競爭。

我們可以發現線程 1 在開始的時候比較幸運,獲得了鎖,最先開始執行任務,但是,由於他比較耗時,最後鎖超時自動釋放了他都還沒執行完。

因此,線程 2 和線程3 的機會來了。

而這一輪,線程2 比較幸運,得到了鎖。

可是,當線程2正在執行任務期間,線程1 執行完了,還把線程2的鎖給釋放了。

這就相當於,本來你鎖着門在廁所裏邊尿尿,進行到一半的時候,別人進來了,因爲他配了一把和你一模一樣的鑰匙!這就亂套了啊

因此,我們需要安全的釋放鎖——“不是我的鎖,我不能瞎釋放”。

所以,我們在加鎖的時候,就需要標記“這是我的鎖”,在釋放的時候在判斷 “ 這是不是我的鎖?”。

這裏就需要在釋放鎖的時候加上邏輯判斷,合理的邏輯應該是這樣的:

1. 線程1 準備釋放鎖 , 鎖的key 爲 mylock  鎖的 value 爲 thread1_magic_num
2. 查詢當前鎖 current_value = get mylock
3. 判斷    if current_value == thread1_magic_num -- > 是  我(線程1)的鎖
          else                                   -- >不是 我(線程1)的鎖
4. 是我的鎖就釋放,否則不能釋放(而是執行自己的其他邏輯)。 

爲了實現上面這個邏輯,我們是無法通過 redis 自帶的命令直接完成的

如果,再寫複雜的代碼去控制釋放鎖,則會讓整體代碼太過於複雜了。

所以,我們引入了lua腳本。

結合Lua 腳本實現釋放鎖的功能,更簡單,redis 執行lua腳本也是原子的,所以更合適,讓合適的人幹合適的事,豈不更好。

Lua實現分佈式鎖 :👇

加鎖:

--[[
思路:
   1.用2個局部變量接受參數
   2.由於redis內置lua解析器,執行加鎖命令
   3.如果加鎖成功,則設置超時時間
   4.返回加鎖命令的執行結果
]]
local key = KEYS[1]
local value = KEYS[2]

local rs1 = redis.call('SETNX',key,value)
if rs1 == true
then
   redis.call('SETEX', key,3600, value)
end

return rs1

解鎖:

--[[
思路:
   1.接受redis傳來的參數
   2.判斷是否是自己的鎖,是則刪掉
   3.返回結果值
]]
local key = KEYS[1]
local value = KEYS[2]

if redis.call('get',key) == value
then
    return redis.call('del',key)
else
    return false   
end

如此,我們便實現了鎖的安全釋放。

同時,我們還需要結合業務邏輯,進行具體健壯性的保證,比如如果結束了一定不能忘記釋放鎖,異常了也要釋放鎖,某種情況下是否需要回滾事務等。

總結這個分佈式鎖使用的過程便是:

  • 加鎖時 key 同,value 不同。
  • 釋放鎖時,根據value判斷,是不是我的鎖,不能釋放別人的鎖。
  • 及時釋放鎖,而不是利用自動超時。
  • 鎖超時時間一定要結合業務情況權衡,過長,過短都不行。
  • 程序異常之處,要捕獲,並釋放鎖。如果需要回滾的,主動做回滾、補償。保證整體的健壯性,一致性。

用redis做分佈式鎖真的靠譜嗎???

  上面的文字中,我們討論如何使用redis作爲分佈式鎖,並討論了一些細節問題,如鎖超時的問題、安全釋放鎖的問題。

目前爲止,似乎很完美的解決的我們想要的分佈式鎖功能。然而事情並沒有這麼簡單,用redis做分佈式鎖並不“靠譜”。

不靠譜的情況:👇

  比如在 Sentinel 集羣中,主節點掛掉時,從節點會取而代之,客戶端上卻並沒有明顯感知。

原先第一個客戶端在主節點中申請成功了一把鎖,但是這把鎖還沒有來得及同步到從節點,主節點突然掛掉了。

然後從節點變成了主節點,這個新的節點內部沒有這個鎖,所以當另一個客戶端過來請求加鎖時,立即就批准了。

這樣就會導致系統中同樣一把鎖被兩個客戶端同時持有,不安全性由此產生。

不過這種不安全也僅僅是在主從發生 failover 的情況下才會產生,而且持續時間極短,業務系統多數情況下可以容忍。

redlock:

爲了解決故障轉移情況下的缺陷,Antirez 發明了 Redlock 算法,使用redlock算法,需要多個redis實例,加鎖的時候,它會想多半節點發送 setex mykey myvalue 命令,只要過半節點成功了,那麼就算加鎖成功了。

釋放鎖的時候需要想所有節點發送del命令。

這是一種基於【大多數都同意】的一種機制。

感興趣的可以查詢相關資料。在實際工作中使用的時候,我們可以選擇已有的開源實現,python有redlock-py,java 中有Redisson redlock。

爲了使用 Redlock,需要提供多個 Redis 實例,這些實例之前相互獨立沒有主從關係。

同很多分佈式算法一樣,redlock 也使用「大多數機制」。

加鎖時,它會向過半節點發送 set(key, value, nx=True, ex=xxx) 指令,只要過半節點 set成功,那就認爲加鎖成功。

釋放鎖時,需要向所有節點發送 del 指令。

不過 Redlock 算法還 需要考慮出錯重試、時鐘漂移等很多細節問題,同時因爲 Redlock 需要向多個節點進行讀寫,意味着相比單實例 Redis 性能會下降一些。

Redlock 使用場景:

如果你很在乎高可用性,希望掛了一臺 redis 完全不受影響,那就應該考慮 redlock。

不過代價也是有的,需要更多的 redis 實例,性能也下降了,代碼上還需要引入額外的library,運維上也需要特殊對待,這些都是需要考慮的成本,使用前請再三斟酌。

 


人生無常大腸包小腸

 

引文:https://www.cnblogs.com/dalianpai/p/12709408.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章