如何使用redis實現分佈式鎖--抄自小灰

Redis分佈式鎖的基本流程並不難理解,但要想寫得盡善盡美,也並不是那麼容易,分佈式鎖的三個核心要素:
1、加鎖
最簡單的辦法是使用setnx命令。key是鎖的唯一標識,按業務來決定命名。比如想要給一種商品的秒殺活動加鎖,可以給key命名爲“lock_sale_商品ID”,而value設置成什麼呢?暫時可以考慮設置爲1.
即:setnx(key,1)
當一個線程使用setnx返回1,則說明key原來不存在,該線程成功獲取了鎖。當一個線程執行setnx返回0,說明key已經存在,搶鎖失敗。

2、解鎖
有加鎖就得有解鎖,當得到鎖的線程執行完任務,需要釋放鎖,以便其他線程可以進入,釋放鎖的最簡單的命令爲執行del指令。如 del(key)
釋放鎖以後,其他線程就可以繼續執行setnx命令來獲取鎖。

3、鎖超時
如果一個得到鎖的線程在執行任務的過程中掛掉,來不及顯示的釋放鎖,這塊資源將會永遠被鎖住,別的線程也進不來。所以,setnx的key必須設置一個超時時間,以保證即使沒有被顯示釋放,這把鎖也要在一定時間後自動釋放,setnx不支持超時參數,使用expire可以設置鎖的超時時間。

4、問題1:setnx和expire的非原子性
在這裏插入圖片描述
在這裏插入圖片描述
如上所示,線程A執行完setnx,還沒有執行expire指令,此時突然線程A掛掉了,那麼此時這個鎖豈不是就是永久鎖住了?

5、代替setnx的set指令
如下所示:
在這裏插入圖片描述
其實實際工作中,我使用到的命令是下面這種。
在這裏插入圖片描述

6、問題2:del導致誤刪
set和expire非原子性的問題解決後,又有可能出現如下情況:
在這裏插入圖片描述
在這裏插入圖片描述
假如線程A成功得到了鎖,並且設置的超時時間是30秒。但是假如線程A執行的速度很慢,超過了30秒還沒有執行完。而鎖過期後自動釋放了,線程B獲取到了鎖。而當線程B還在執行過程中,線程A又執行完了任務,執行del指令來釋放鎖。那麼問題就大發了。線程A實際上刪除的是線程B加的鎖。
在這裏插入圖片描述

那麼如何避免這個問題呢?就需要在del釋放鎖之前做一個判斷,驗證當前的鎖是不是自己加的鎖(原來如此)
即在加鎖的時候把當前的線程ID當做value,並在刪除之前驗證key對應的value是不是自己線程的ID。
在這裏插入圖片描述
但是這樣,又隱含了一個新的問題:判斷和釋放鎖是兩個獨立操作,不是原子性。聽說使用Lua腳本可以實現原子性

在這裏插入圖片描述

7、問題3:併發出現的情況
解決了上述可能存在的線程誤刪key的情況,但是本質上,還是由於兩個線程同時去訪問了代碼。

可以讓獲得鎖的線程開啓一個守護線程,用來給快要過期的鎖延期。當線程執行完任務後,就顯式關掉守護線程。
在這裏插入圖片描述

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