天天看點

面試官問redis分布式鎖,如何設計才能讓他滿意?

前言

對于分布式鎖的問題我也查過很多資料,感覺很多方式實作的并不完善,或者看着雲裡霧裡的,不知是以然,于是就整理了這篇文章,希望對您有用,有寫的不對的地方,歡迎留言指正。

首先咱們來聊聊什麼是分布式鎖,到底解決了什麼問題?直接看代碼

很簡單的一個場景,使用者下單,咱們查詢商品庫存夠不夠,不夠的話直接傳回庫存不足類似的錯誤資訊,如果庫存夠的話直接在資料庫中庫存-1,然後傳回成功,在業務邏輯上這段代碼是沒有什麼問題的。

<code>但是,這段代碼是存在嚴重的問題的。</code>

如果庫存隻剩 1,并且在并發比較高的情況下,比如兩個請求同時執行了這段代碼,同時查到庫存為 1,然後順利成章的都去資料庫執行 stock-1 的操作,這樣庫存就會變成-1,然後就會引發超賣的現象,剛才說的是兩個請求同時執行,如果同時幾千個請求打過來,可見造成的損失是非常大的。于是呢有些聰明人就想了個辦法,辦法如下。

大家都知道 redis 有個 setnx 指令,不知道的話也沒關系,我已經幫你查過了

面試官問redis分布式鎖,如何設計才能讓他滿意?

我們把上面的代碼優化一下

version-1

第一次請求進來會去 setNx,當然結果是傳回 true,因為 lock_key 不存在,然後下面業務邏輯正常進行,任務執行完了之後把lock_key删除掉,這樣下一次請求進來重複上述邏輯

第二次請求進來同樣會去執行 setNx,結果傳回 false,因為lock_key已經存在,然後直接傳回錯誤資訊(你雙11搶購秒殺産品的時候給你傳回的系統繁忙就是這麼來的),不執行庫存減 1 的操作

有的同學可能有疑惑,咱們不是說高并發的情況下麼?要是兩個請求同時 setNx 的話擷取的結果不都是 true 了,同樣會同時去執行業務邏輯,問題不是一樣沒解決麼?但是大家要明白 redis 是單線程的,具備原子性,不同的請求執行 setnx 是順序執行的,是以這個是不用擔心的。

看似問題解決了,其實并不然。

我們這裡僞代碼寫的簡單,查詢一下庫存,然後減1操作而已,但是真實的生産環境中的情況是非常複雜的,在一些極端情況下,程式很可能會報錯,崩潰,如果第一次執行加鎖了之後程式報錯了,那這個鎖永遠存在,接下來的請求永遠也請求不進來了,是以咱們繼續優化

version-2

在setnx的時候給加上過期時間,這樣至少不會讓鎖一直存在成為死鎖

做try catch處理,萬一程式抛出異常把鎖删掉,也是為了解決死鎖問題

這次是把死鎖問題解決了,但是問題還是存在,大家可以先想一想還存在什麼問題再接着往下看。

存在的問題如下

我們的過期時間是5秒鐘,萬一這個請求執行了6秒鐘怎麼辦?超出的那一秒,跟沒有加鎖有什麼差別?其實不僅僅如此,還有一個更嚴重的問題存在。比如第二個請求也是執行6秒,那麼在第二個請求在超出的那1秒才進來的時候,第一個請求執行完了,當然會删除第二個請求加的鎖,如果一直并發都很大的話,鎖跟沒有加沒什麼差別。

針對上述問題,最直接的辦法是加長過期時間,但是這個不是解決問題的最終辦法。把時間設定過長也會産生新的問題,比如各種原因機器崩潰了,需要重新開機,然後你把鎖設定的時間是1年,同時也沒有delete掉,難道機器重新開機了再等一年?另外這樣設定固定值的解決方案在計算機當中是不允許的,曾經的“千年蟲”問題就是類似的原因導緻的

在加逾時時間的時候一定要注意一定是一次性加上,保證其原子性,不要先setnx之後,再設定expire_time,這樣的話萬一在setnx之後那一個瞬間系統挂了,這個鎖依然會成為一個永久的死鎖

其實上述問題的主要原因在于,請求1會删掉請求2的鎖,是以說鎖需要保證唯一性。

咱們接着優化

version-3

我們在每個請求生成了唯一client_id,并且把該值寫入了lock_key中

在最後删除鎖的時候會先判斷這個lock_key是否是該請求生成的,如果不是的話則不會删除

但是上面方案還有問題,我們看最後 redis是先進行了get操作判斷,然後再删除,是兩步操作,并沒有保證其原子性,redis的多步操作可以用lua腳本來保證原子性,其實看到lua也不需要感覺太陌生,他就是一種語言而已,在這裡的作用是把多個redis操作打包成一個指令去執行,保證了原子性而已

version-4

這樣封裝之後,分布式鎖應該就比較完善了。當然我們還可以進一步的優化一下使用者體驗

現在比如一個請求進來之後,如果請求被鎖住,會立即傳回給使用者請求失敗,請重新嘗試,我們可以适當的延長一點這個時間,不要立即傳回給使用者請求失敗,這樣體驗會更好

具體方式為使用者請求進來如果遇到了鎖,可以适當的等待一些時間之後重試,重試的時候如果鎖釋放了,則這次請求就可以成功

version-5

當然上面的分布式鎖還是不夠完善的,比如redis主從同步延遲,就會産生問題,像java中redission實作的思想是非常好的,大家感興趣可以看看源碼,今天就聊到這裡,感興趣的朋友可以留言大家一起讨論

繼續閱讀