麻煩你們幫我投一票哈,謝謝redis
什麼是分佈式鎖
針對共享內存模型的程序(例如JAVA程序),鎖就是一個很是經常使用的機制。 通常簡單分爲悲觀鎖和樂觀鎖。悲觀鎖就是你獲取這塊數據的鎖以後,別人就沒法訪問或操做這塊數據,直到你釋放這個鎖。樂觀鎖通常就是CAS更新。 在單進程內內存的鎖,只控制進程內數據的,就是非分佈式鎖。相反的,跨進程,須要鎖住多個進程訪問數據的鎖就是分佈式鎖。緩存
悲觀鎖通常由Redis的SETNXEX
實現,Key 爲資源名,EX 設置一個合理的超時時間, Value 設置爲一個客戶端生成的在超時時間內不會重複的隨機字符串。安全
lua腳本特性說明
因爲redis是單線程的,執行lua腳本的時候,不會同時執行其餘客戶端命令,這必定程度上保證了併發安全網絡
單進程Redis分佈式悲觀鎖實現思路
悲觀鎖通常由Redis的SETNX
實現,經過SETNX
獲取一個鎖,釋放鎖就把這個Key刪除便可。 例如,獲取一個名爲TestLock的鎖,經過SETNX TestLock TestLockValue
,TestLockValue隨意設置一個值。SETNX
是SET IF NOT EXISTS,若是不存在就會設置成功。 設置成功會返回1,沒設置成功會返回0。返回1表明獲取鎖成功,0表明沒獲取成功。 釋放鎖調用DEL
命令,經過DEL TestLock
釋放鎖。返回1表明釋放鎖成功,0表明沒有這個鎖。併發
可是若是這麼簡單實現,在以下場景時,會有問題:dom
- 客戶端1獲取鎖成功。
- 客戶端1死掉了,沒能釋放鎖。
- 其餘客戶端再也獲取不了這個鎖了。
因此,考慮這個,咱們通常會給鎖設置一個超時時間。在超過這個超時時間以後,redis會自動把這個key刪除掉。這樣,在客戶端1死掉了以後,不會致使這個鎖永遠不釋放。分佈式
這樣redis命令集合就變成了: 獲取鎖:lua
> SETNX TestLock TestLockValue > EXPIRE TestLock 8
釋放鎖:url
> DEL TestLock
因爲SETNX
還有EXPIRE
是兩條命令,在以下場景還可能出問題:.net
- 客戶端1執行
SETNX TestLock TestLockValue
成功。 - 客戶端1死掉了。沒有設置過時時間。
- 其餘客戶端再也獲取不了這個鎖了。
Redis 2.8 版本中做者加入了 set 指令的擴展參數,使得 setnx 和 expire 指令能夠一塊兒執行
這樣redis命令集合就變成了: 獲取鎖:
> SET TestLock TestLockValue EX 5 NX
釋放鎖:
> DEL TestLock
也能夠經過Redis事務來執行這兩條命令,可是在Redis被kill掉時,可能只有部分命令被執行,因此仍是經過上面的命令更安全簡潔。
但在以下場景又會有新的問題:
- 客戶端1獲取鎖成功。
- 客戶端1在某個操做上阻塞了很長時間(GC等等)或者很長時間的網絡延遲。
- 過時時間到了,鎖自動釋放了。
- 客戶端2獲取到了對應同一個資源的鎖。
- 客戶端1從阻塞中恢復過來,釋放掉了客戶端2持有的鎖。
爲了不這種狀況發生,咱們通常會將鎖的KEY的VALUE設置爲一個隨機值,這個隨機值只要在一段連續時間內不會重複就行。在釋放鎖的時候,判斷當前鎖的VALUE與本身設置的是否一致,只有一致的時候纔會釋放。
這樣redis命令集合就變成了: 獲取鎖:
> SET TestLock RandomValue EX 5 NX
釋放鎖:
> GET TestLock //程序判斷是否一致 > DEL TestLock
這個在以下場景也會有問題:
- 客戶端1獲取鎖成功。
- 客戶端1訪問共享資源。
- 客戶端1爲了釋放鎖,先執行’GET’操做獲取隨機字符串的值。
- 客戶端1判斷隨機字符串的值,與預期的值相等。
- 客戶端1因爲某個緣由阻塞住了很長時間。
- 過時時間到了,鎖自動釋放了。
- 客戶端2獲取到了對應同一個資源的鎖。
- 客戶端1從阻塞中恢復過來,執行DEL操縱,釋放掉了客戶端2持有的鎖。
因此,釋放鎖必定要放在一個事務內,經過lua腳本。也就是:
傳入Lua腳本參數 TestLock,當前程序緩存的隨機值 判斷TestLock的值與傳入值是否一致,一致則刪除 返回是否解鎖成功
這樣在Redis單進程一直正常運行的狀況下,分佈式悲觀鎖就實現了。
每日一刷,輕鬆提高技術,斬獲各類offer: