分佈式鎖是控制分佈式系統或不一樣系統之間共同訪問共享資源的一種鎖實現 若是不一樣的系統或同一個系統的不一樣主機之間共享了某個資源時,每每經過互斥來防止彼此干擾。java
使用setnx命令加鎖,key是鎖的惟一標識,能夠根據業務來命名,value爲當前線程的ID或者UUID(後面介紹緣由) 好比扣減商品庫存,key但是 lock_stock_upc ,value能夠爲當前線程ID。python
若是一個獲得鎖的線程在執行任務的過程當中掛掉,來不及顯式地釋放鎖,這塊資源將會永遠被鎖住,別的線程再也別想進來。那麼這個鎖就永遠被沒法獲取到 因此,咱們須要給setnx的key必須設置一個超時時間,以保證在異常狀況下即便鎖沒有被顯式釋放,這把鎖也要在必定時間後自動釋放。redis
當獲得鎖的線程執行完任務,須要釋放鎖,以便其餘線程能夠進入。釋放鎖的最簡單方式是執行del指令, del(key)釋放鎖以後,其餘線程就能夠繼續執行setnx命令來得到鎖。算法
因爲set和expire的非原子性,會致使一個異常狀況 bash
因此要保證SETNX和SETEX(設置過時時間)這2個命令一塊兒執行,要麼都成功,要麼都失敗,保證其原子性。服務器
假如某線程成功獲得了鎖,而且設置的超時時間是30秒。若是某些緣由致使線程B執行的很慢很慢,過了30秒都沒執行完,這時候鎖過時自動釋放,線程B獲得了鎖。分佈式
線程A執行完了任務,線程A接着執行del指令來釋放鎖。但這時候線程B還沒執行完,線程A實際上刪除的是線程B加的鎖。性能
怎麼避免這種狀況呢?能夠在del釋放鎖以前作一個判斷,驗證當前的鎖是否是本身加的鎖。ui
至於具體的實現,能夠在加鎖的時候把當前的線程ID當作value,並在刪除以前驗證key對應的value是否是本身線程的ID。 這就是前面說的設置value的時候要設置成uuid或者線程ID的緣由。spa
if(threadId .equals(redisClient.get(key))){
del(key)
}
複製代碼
然而因爲,if判斷和釋放鎖是兩個獨立操做,不是原子性,因此採用Lua腳本釋放鎖。後面介紹實現。
經過以上可知,要想實現一個可靠的分佈式鎖,設計鎖的時候須要考慮一下要素:
SET key value NX PX 30000
value是由客戶端生成的一個隨機字符串,至關因而客戶端持有鎖的標誌
NX表示只有key值不存在的時候才能SET成功,至關於只有第一個請求的客戶端才能得到鎖
PX 30000表示這個鎖有一個30秒的自動過時時間。
複製代碼
爲了防止客戶端1得到的鎖,被客戶端2給釋放,採用下面的Lua腳原本釋放鎖
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
複製代碼
假如在redis sentinel集羣中,咱們具備多臺redis,他們之間有着主從的關係,例如一主二從。咱們的set命令對應的數據寫到主庫,而後同步到從庫。當咱們申請一個鎖的時候,對應就是一條命令 setnx mykey myvalue ,在redis sentinel集羣中,這條命令先是落到了主庫。假設這時主庫down了,而這條數據還沒來得及同步到從庫,sentinel將從庫中的一臺選舉爲主庫了。這時,咱們的新主庫中並無mykey這條數據,若此時另一個client執行 setnx mykey hisvalue , 也會成功,即也能獲得鎖。這就意味着,此時有兩個client得到了鎖。這不是咱們但願看到的,雖然這個狀況發生的記錄很小,只會在主從failover的時候纔會發生,大多數狀況下、大多數系統均可以容忍,可是不是全部的系統都能容忍這種瑕疵。
爲了解決故障轉移狀況下的缺陷,Antirez 發明了 Redlock 算法,使用redlock算法,須要多個redis實例,加鎖的時候,它會想多半節點發送 setex mykey myvalue 命令,只要過半節點成功了,那麼就算加鎖成功了。釋放鎖的時候須要想全部節點發送del命令。這是一種基於【大多數都贊成】的一種機制。咱們能夠選擇已有的開源實現,python有redlock-py,java 中有Redisson redlock。
redlock確實解決了上面所說的「不靠譜的狀況」。可是,它解決問題的同時,也帶來了代價。你須要多個redis實例,你須要引入新的庫 代碼也得調整,性能上也會有損。因此,果真是不存在「完美的解決方案」,咱們更須要的是可以根據實際的狀況和條件把問題解決了就好。