首頁  >  文章  >  資料庫  >  淺析Redis中的鎖,聊聊Redlock(redis分散式鎖)

淺析Redis中的鎖,聊聊Redlock(redis分散式鎖)

青灯夜游
青灯夜游轉載
2022-01-20 10:07:3013774瀏覽

這篇文章帶大家聊聊Redis中的鎖,介紹一下為什麼要用鎖,真的需要Redlock(redis分散式鎖)嗎,希望對大家有幫助!

淺析Redis中的鎖,聊聊Redlock(redis分散式鎖)

為什麼要用鎖

我待過的k12教育公司,我們當時有個業務場景是這樣的。業務這邊要給學生排課,偶爾會回饋學生的課時明明充足的但是卻提示課時不足,等再刷新一遍頁面卻發現學生的課時已經不夠了。更可怕的是,偶爾會有學生的課時被扣成負數(公司被白嫖課時)。 【相關推薦:Redis影片教學

再例如下面這個範例

淺析Redis中的鎖,聊聊Redlock(redis分散式鎖)

上面的這兩個問題都是並發給我們的業務帶來的問題。解決這個問題的核心就是,同一時間只能允許有一個請求來對這些敏感(重要)的資料進行讀寫操作。所以這個時候就要使用到分散式鎖來限製程式的並發執行。

setnx有哪些問題

我們先來看看用Redis如何實作分散式鎖,想必大家都很熟悉。例如針對我文章開頭講的學生排課的問題我們就可以這樣加鎖

淺析Redis中的鎖,聊聊Redlock(redis分散式鎖)

#這就是我們常規使用setnx來實現鎖的方式。

現在我們假設有這樣一個場景。 A請求拿到鎖到了第2步給學生排課的時候程序掛了,沒有釋放鎖。那麼這個鎖就成了死鎖,下一個操作同一個學生的請求永遠就拿不到鎖,那麼這個學生就沒辦法被排課了。這個時候都需要手動去把鎖釋放掉。

為了解決死鎖的問題,我們給鎖加一個過期時間。

淺析Redis中的鎖,聊聊Redlock(redis分散式鎖)

加上過期時間之後,如果A請求沒有主動釋放鎖,在鎖定過期之後也會主動釋放,這樣B請求一樣可以取得鎖定處理業務邏輯。但是如果在加過期時間的時候也就是在第1步和第2步之間程式崩潰。那還是會出現死鎖的問題。這個問題的根源就在於setnx和expire這兩個指令不是原子指令。所以如果setnx和expire能夠要不是全部執行要嘛一個都不執行那該有多好。

為此在Redis2.8之前社區湧現了一大批擴充包來解決這個問題。官方為了治理該亂象,在2.8版本中加入了set指令的擴充參數使得setnx和expire指令可以一起執行,所以現在我們使用分散式鎖定應該是這樣了

淺析Redis中的鎖,聊聊Redlock(redis分散式鎖)

這樣看起來已經很完美了,已經達成了我們的期望「setnx和expire能夠要麼全部執行要麼一個都不執行那該多好」。我們再假設現在有以下場景:

A請求現在取得到了鎖,鎖的超時時間設定的是5秒。到了第2步驟執行業務邏輯,結果因為某些原因5秒之後業務邏輯還沒執行完,此時鎖由於逾時自動釋放了。這時候B請求也來了,拿到鎖之後開始執行業務邏輯。 A請求這個時候業務邏輯執行完了,開始執行第三步,釋放了鎖。而這個時候鎖是B請求拿到的,結果被A請求釋放了。那麼C請求就可以拿到鎖了。這時候B請求和C請求就會導致並發問題了。所以可以從這個例子看出來,在分散式鎖中過期時間的設定非常重要,如果設定的時間小於這個介面的回應時間那麼仍然會產生並發問題。所以我們可以參考介面回應時長的監控來設定鎖的過期時間。

Redlock

我們上述的方案都是基於單點的Redis的實作方式。單點的Redis實現分散式鎖基本上可以滿足95%的業務場景。剩下的5%就是對資料一致性要求極為嚴苛並且對於鎖丟失的0容忍的業務場景。這時候就得考慮Redlock了。至於單點的Redis即使透過sentinel保證高可用,如果這個master節點因為某些原因發生了主從切換,如果資料主從資料同步不及時那麼勢必會有資料遺失,那麼就會出現鎖定遺失的情況。

假設存在多個Redis實例,這些節點是完全獨立的,不需要使用複製或任何協調資料的系統,我們假設有5個Redis master節點,客戶端為了取到鎖,步驟將會變成這樣:

  • 以毫秒為單位取得目前的伺服器時間

  • 嘗試使用相同的key和隨機值來取得鎖,客戶端對每一個機器取得鎖時都應該有一個超時時間,例如鎖的過期時間為10s,那麼取得單一節點鎖的超時時間就應該是5到50毫秒左右,他這樣做的目的是為了確保客戶端與故障的機器連接不耗費多餘的時間!超時間時間內未取得資料就放棄該節點,從而去下一個Redis節點取得。

  • 取得完成後,取得目前時間減去步驟一取得的時間,當且僅當客戶端從半數以上(這裡是3個節點)的Redis節點取得到鎖且取得鎖的時間小於鎖額逾時時間,則證明該鎖生效!

  • 如果取到了鎖,key的真正有效時間等於有效時間減去取得鎖所使用的時間(步驟3計算的結果)。

  • 如果取得鎖的機器不滿足半數以上,或者鎖的逾時時間計算完畢後為負數等異常操作,則係統會嘗試解鎖所有實例,即便某些Redis實例根本就沒有加鎖成功,防止某些節點取得到鎖定但是客戶端沒有得到回應而導致接下來的一段時間不能被重新取得鎖定

所以我們看出,redlock其實是比單點Redis看起來更可靠的鎖。

如果你跟我一樣是Node.js程式設計師那麼剛好有第三方函式庫redlock直接使用。

我們真的需要redlock嗎

關於redlock其實也有另外一種聲音,Martin Kleppmann(劍橋大學的研究員,從事資料庫、分散式系統與資訊安全交叉領域的TRVE DATA專案)寫過一篇blog#發表了關於對redlock的一些看法,有興趣的可以看看。 Redis作者Salvatore也對這篇文章的疑問做出了一些回應,還挺有意思的。作者的blog主要的觀點如下:

分散式鎖定的用途無非兩種:

  • 效率:使用鎖定可以避免不必要地做同樣的工作兩次(例如一些昂貴的計算)。如果鎖定失敗並且兩個節點最終完成相同的工作,結果是成本略有增加(您最終向AWS 支付的費用比其他情況多5 美分)或帶來輕微的不便(例如,用戶最終兩次收到相同的電子郵件通知)。
  • 正確性:使用鎖定可以防止並發進程相互幹擾並破壞系統狀態。如果鎖定失敗並且兩個節點同時處理相同數據,則結果是文件損壞、數據丟失、永久性不一致、給患者服用的藥物劑量錯誤或其他一些非常嚴重的問題。

如果是為了效率,則根本沒有必要承擔Redlock 的成本和複雜性,鎖定遺失導致多發幾次郵件和運行5 個Redis 伺服器的成本相比,最好只使用單一Redis實例。如果你使用的是單一 Redis 實例,Redis 節點突然斷電或崩潰,或出現其他問題,這個時候當然會遺失鎖。但是如果你只是將鎖用作效率優化,而且這種崩潰不會經常發生,那就沒什麼大不了的。這種「沒什麼大不了」的場景是 也剛好是Redis優秀的地方。至少如果依賴單一 Redis 實例,那麼查看系統的每個人都能夠更方便的定位問題。

如果是為了正確性,那麼嚴格來講,redlock根本不具有強一致的嚴格性。舉了一些例子

  • 時序和系統時鐘做出了危險的假設,對每台伺服器的時脈強烈依賴。因為有系統有GC的存在,做GC的時整個伺服器是夯住的,時間也就停滯了,所以我們不能夠對時鐘有強烈依賴。

  • 沒有令牌。客戶端每次取得鎖的時候服務端沒有下發令牌,服務端應該校驗每次操作的時候客戶端的令牌要與服務端目前的令牌一致才難操作鎖。

作者主要就是以上這些個觀點,如果有興趣的還是推薦去看看原文吧。

更多程式相關知識,請造訪:程式設計入門! !

以上是淺析Redis中的鎖,聊聊Redlock(redis分散式鎖)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除