關於redis分布式鎖的使用

2021-07-13 05:13:20 字數 2811 閱讀 4600

背景:比如我有100張點卡,有兩台伺服器同時進行賣這個點卡,但是今天我就想賣10張,超出10張我就不賣了,在多執行緒的額情況下很容易出現賣出了11張甚至更多,這也是超賣的問題,從實現來說也可能出現兩個人獲取到的是同一張點卡,那麼也是不可取的

出於乙個涉世未深,對那些高大上的東西充滿好奇的我希望可以使用自己沒用過的東西來解決上面的問題,所以我選擇了使用分布式鎖來解決分布式情況下超賣的問題 

如果程式只在一台伺服器裡跑,那麼我們可以很簡單的在需要控制同步的地方使用鎖lock,或者使用sychronized,但是在多台伺服器的情況下如此使用是不可以的,但是可以選擇在資料庫層面通過悲觀鎖、樂觀鎖解決,亦或redis的訊息佇列..還有其他的一些方案

言歸正傳,本人將闡述分布式鎖從無到有,遇到了哪些問題,網上有兩種redis分布式鎖的實現方案

1.採用setnx配合getset超時方案

步驟如下:

1.使用setnx

(lock,時間戳)

方式,進行獲取鎖,如果獲取到鎖,返回為1

dosomething

(),沒有獲取到返回為0並執行第2步

2.使用get

(lock)

方法去獲取lock對應的時間戳,並判斷時間是否已經超時,如果超時了進入第3步

3.使用getset方法將新鎖新增到redis中,並判斷getset的返回值,是否和2獲取到的value值相同,如果相同說明獲取到了鎖 dosomething

(),所以不同則說明鎖已經被別人獲取到了

4.執行成功釋放鎖del

dosomething

()為自己操作的內容

以上的實現是有問題的,情況在於,如果1號執行緒在執行第3步並且也獲取到鎖了的時候2號執行緒執行了第4步,那麼就會出現3號執行緒執行第1步的時候也獲取到鎖了,有點繞口,其實就是說,如果乙個執行緒通過getset方法發現占用鎖的執行緒超時了而且我也獲取到新鎖了,那個超時的執行緒正好也執行完了做了釋放鎖的操作,那麼肯定會出現第三個鎖通過 setnx方法又獲取到了新鎖。

如果你真正的去嘗試寫了這麼一段**,你也會發現這裡會出現乙個問題,經常報空指標異常,問題就在於,有執行緒釋放鎖了之後存在乙個執行緒過了get操作,然後將獲取的null做超時判斷。

2.迴圈採用setnx方案

步驟如下:

1.使用setnx(lock,時間戳)方式,進行獲取鎖,如果獲取到鎖,返回為1並執行第二步,沒有獲取到返回為0重新執行第1步

2.通過expire設定超時時間,dosomething();

3.執行成功釋放鎖del;

以上的方法實現獲取覺得沒有任何的問題,但是依然存在很大的隱患,隱患在於,如果某個執行緒在第1步獲取到鎖了但是在執行第2步的時候出現了異常導致設定超時時間失敗,那麼該鎖就一直不會失效,最後導致下面的所有的執行緒都無法再獲取到鎖

就在我覺得redis無法實現分布式鎖的時候,我在公司的關於redis方法的封裝中發現了一句**

string

result = jedis.set(key, value, "nx", "ex", seconds);

於是我上網查閱了一下資料,了解到這一句的**的意思是:設定鍵key,超時時間為seconds,並且如果該鍵存在則設定失敗,該鍵不存在則設定成功

於是我將第二個方案重新改變了一下

步驟如下:

1.set

(key, value, "nx", "ex", seconds)

方式獲取鎖,如果獲取到鎖,執行2,沒有繼續執行1

2.dosomething

3.執行成功釋放鎖del

這個時候我覺得下面的寫法已經很正確了

//獲取鎖

public boolean requirelock(string key, string value ,int seconds) 

//釋放鎖

public boolean releaselock(string key)

就在那麼一天我看到了乙個redis的文件,裡面有那麼幾段內容

分布式鎖應該參考the redlock algorithm的實現,應該這個方法只是複雜一點,但是卻能保證更好的使用

避開上面說的不提,下面的一段內容吸引了我的注意

可以通過如下優化使得上面的鎖系統變得更加棒:

不要設定固定的字串,而是設定為隨機的大字串,可以稱為token。

通過腳步刪除指定鎖的key,而不是del命令。

上述優化方法會避免下述場景:a客戶端獲得的鎖(鍵key)已經由於過期時間到了被redis伺服器刪除,但是這個時候a客戶端還去執行del命令。而b客戶端已經在a設定的過期時間之後重新獲取了這個同樣key的鎖,那麼a執行del就會釋放了b客戶端加好的鎖。

突然覺得好像是那麼一回事哦

所以我給**又做了修改

//獲取鎖

public boolean requirelock(string key, string value ,int seconds)

//釋放鎖

public boolean releaselock(string key, string value)

return

false;

}

上面的**要保證value是乙個隨機字串。如果不用時間戳的更好

附言:可能很多人覺得使用最後的方案應該就成功了,但是筆者真真實實的遇到了乙個問題——重排序,就是dosometing()方法的在releaselock方法後面執行了,導致我在測試的時候,發現存在兩個執行緒同時獲取到了鎖的情況,當我加了同步**塊之後,這個問題也就不再存在了。

分布式鎖 使用Redis實現分布式鎖

關於分布式鎖的實現,我的前一篇文章講解了如何使用zookeeper實現分布式鎖。關於分布式鎖的背景此處不再做贅述,我們直接討論下如何使用redis實現分布式鎖。關於redis,筆主不打算做長篇大論的介紹,只介紹下redis優秀的特性。支援豐富的資料型別,如string list map set zs...

redis分布式鎖

redis分布式鎖 直接上 我寫了四個redis分布式鎖的方法,大家可以提個意見 第一種方法 redis分布式鎖 param timeout public void lock long timeout thread.sleep 100 catch exception e override publi...

Redis分布式鎖

分布式鎖一般有三種實現方式 1.資料庫樂觀鎖 2.基於redis的分布式鎖 3.基於zookeeper的分布式鎖.首先,為了確保分布式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件 互斥性。在任意時刻,只有乙個客戶端能持有鎖。不會發生死鎖。即使有乙個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保...