目錄
1.什么是分布式鎖?
2.分布式鎖的實現
3.引入過期時間
4.引入校驗ID
5.引入lua腳本:
6.引入看門狗(watch dog)
7.引入redislock算法:
1.什么是分布式鎖?
?在 分布式系統中,會出現多個節點同時訪問同一個公共資源, 此時就需要通過鎖來作互斥控制,避免出現類似于多線程中的"線程安全"問題.
2.分布式鎖的實現
分布式鎖實現的本質是 通過一個鍵值對來標識鎖的狀態.在redis上寫入一個key-val.
mysql的事務也能起到避免線程安全問題實現查詢+修改操作,但是在實際使用的過程中,不一定是mysql, 也可能是其他存儲介質.
加鎖: 不能存在時就設置,存在就設置失敗,可以使用redis中的setnx 命令可以實現這個場景.
解鎖: 使用del命令實現.
3.引入過期時間
當對一個數據加鎖后,若還未執行解鎖的邏輯,程序突然崩潰了,就會導致加了鎖無法正常釋放.
可以通過設置一個過期時間機制,達到過期時間,自動刪除.達到鎖被自動釋放的效果.
通過 set ex nx命令來實現.
setnx ,expire這兩個命令也能實現設置過期時間的功能,但不能通過這個命令來實現.
因為redis的命令的原子性,隨能保證不允許被別的命令插隊,但不能保證一個事務中的所有命令是全部執行成功,若中間有命令執行失敗,就無法保證鎖的正確設置了.
通過set nx ex一條命令,一定能保證要么執行成功,要么全部執行失敗.
4.引入校驗ID
當一個服務器對redis設置了一個key-val.,進行了加鎖;
而另一個服務器對redis中的該值進行了誤刪除,這就出現了錯誤的邏輯.
為了解決這個問題,可以引入校驗id,讓key對應的val中存儲加鎖的服務器編號,當進行解鎖時, 只有該編號的服務器對其解鎖才能解鎖成功,其他服務器對其解鎖,都會解鎖失敗.
這樣就避免了誤刪除操作.
5.引入lua腳本:
在一個服務器內部,可能有多個線程.
當兩個線程前后對同一個redis進行解鎖時,由于解鎖的需要先判斷是否是該服務器上鎖的,然后進行解鎖,不是原子性的,可能引發兩次解鎖.
在執行解鎖操作時,服務器一的線程1先執行get命令,判斷是否是該線程加的鎖,在執行del命令之前, 然后又一個線程2,執行get,此時還未解鎖,線程2的get是成功的,
然后線程1執行了del操作,鎖釋放成功,
線程2再執行del操作,就會執行失敗.這就是鎖被重復.
但若在3,4步之間,又有一個服務器2的線程執行set nx ex上操作,此時線程b的del命令由于已經通過了判定, 就會將服務器2剛上的鎖給釋放了,就會出現錯誤.這是釋放鎖的命令不是原子性造成的.
可以通過lua腳本來解決解鎖時命令不是原子的的問題,
lua腳本是一個輕量級的編程語言,內嵌到redis中,使用lua編寫一些程序,然后將這個腳本上傳到redis服務器上,就可以讓客戶來控制redis執行上述lua腳本命令了.
redis執行lua腳本的過程是原子的,相當于一條命令在執行.
6.引入看門狗(watch dog)
設置過期時間,當過期時間到了,還需要繼續使用呢,又要怎樣將過期時間進行續約呢?
使用"動態續約"來實現.
初始情況下,設置一個過期時間,當過期時間馬上到了,還未有線程來釋放鎖,就會自動續約,再加上一段過期時間,時間又快到了,任務還沒執行完,就再續上一段時間.這樣既能保證當任務還沒執行完時,不會自動解鎖,又能保證當程序中途出現崩潰,鎖還未釋放時,就無法自動續約了,當到了過期時間,就自動解鎖了.
7.引入redislock算法:
這里有個問題,當用來加鎖的redis出現故障了,突然掛了,那所有的加鎖信息就沒了.
這就要用之前的知識了,使用主從復制,引入多個從節點,進行主從信息同步,即使主節點掛了,從節點也保存了加鎖的信息.
但這里又有一個問題,主從節點數據同步是存在一個延遲性的,萬一主節點剛收到一個加鎖信息,還沒來得及同步給從節點,主節點就掛了,那樣就出現數據丟失了.
為了解決這個問題,可以使用redislock 算法:使用集群的方法,引入多個redis主節點,每個主節點都創建多個從節點,當要寫入數據時,對每個主節點都執行相同的寫入命令,起到一個備份的作用.
?當執行成功的節點個數超過總主節點的一半時,才視為執行成功,即使有某個主節點掛了,還有別的主節點已經存儲了數據.這樣就能保證加鎖,解鎖的正確性了.