首頁 > 軟體

Redis常見分佈鎖的原理和實現

2022-08-18 14:03:05

前言

Java中的鎖主要包括synchronized鎖和JUC包中的鎖,這些鎖都是針對單個JVM範例上的鎖,對於分散式環境是無效的,那麼基於分散式鎖的如何實現呢?

常見的分散式鎖的實現如下圖:

基於資料庫

悲觀鎖

悲觀鎖(Pessimistic Lock)顧名思義為很悲觀的鎖,每次在拿資料的時候都會上鎖。這樣別人想拿資料就被擋住,直到悲觀鎖被釋放,悲觀鎖中的共用資源每次只給一個執行緒使用,其它執行緒阻塞,用完後再把資源轉讓給其它執行緒,但是在效率方面,處理加鎖的機制會產生額外的開銷,且容易產生死鎖。

實現原理

悲觀並行控制實際上是"先取鎖再存取"的保守策略,為資料處理的安全提供了保證.

具體實現

例如通過悲觀鎖來實現庫存扣減的虛擬碼如下:

// 對於庫存記錄進行行鎖

SELECT *FROM sys_goods s WHERE s.Id='1' FOR UPDATE;

//執行庫存扣減
update sys_stock s set s.stockQty=s.stockQty-#{number} where s.goodId=1 and s.stockQty>0;

//提交事務,自動釋放悲觀鎖。

樂觀鎖

簡介

樂觀鎖是基於資料版本號(version)的機制來實現的。資料庫表新增"version"欄位, 讀取出資料時,將此版本號讀出,在更新過程中,會對版本號進行比較,如果是一致的,則會成功執行本次操作,且版本號加1,如果版本號不一致,則會更新失敗。

實現原理

相對悲觀鎖,樂觀鎖的實現不會使用到資料庫的鎖機制,樂觀鎖的原理使用的CAS的機制來實現的,CAS(Compare-and-Swap)即比較並替換.

  • 1、比較:讀取到了一個值A,在將其更新為B之前,檢查原值是否仍為A(未被其他執行緒改動).
  • 2、設定:如果是未傳送變化,則將A更新為B結束。如果發生變化,則什麼都不做。

具體實現

例如樂觀鎖來實現庫存扣減的虛擬碼如下:

// 查詢庫存記錄,獲取版本號
SELECT stockQty,version FROM sys_goods s WHERE s.Id='1'

//執行庫存扣減,防止出現超賣
update sys_stock s set 
  s.stockQty=s.stockQty-#{number},
  s.version=version+1
  where s.goodId=1 and s.stockQty>0 and version=#{version};

Redis實現分散式鎖

關於Redis分散式鎖的實現,已經在前期的文章中進行了講解,大家可以參考如下文章

Spring Boot 實現Redis分散式鎖原理

Spring Boot 整合Redisson實現分散式鎖詳細案例

Zooker實現分散式鎖

Zookper實現分散式鎖,主要是應用zookeeper節點的臨時和有序性來實現。

加鎖過程

當用戶端1請求時,Zookeeper使用者端會建立一個持久節點Locks節點,如果使用者端1想獲取鎖,會在locks節點下建立臨時節點/node_000000,如果查詢Locks下面所有臨時有序子節點,當自己為最小的節點是則獲取鎖成功。

當用戶端2嘗試獲取鎖時,也會檢視locks下面的臨時節點,判斷自己的節點/node_000001是不是最小,如果不是最小則獲取鎖失敗,使用者端2會向它排序靠前的節點node_000000註冊watch事件,用來監聽node_000000是否存在,雖然搶鎖失敗,但是node_000001進入等待狀態。

釋放鎖的過程

Zookeeper的使用者端業務完成或者使用者端發生故障,都會刪除臨時節點並且釋放鎖。如果是任務完成,使用者端1還會顯式呼叫刪除node_000000的指令。

例如上述圖,使用者端1斷開,臨時節點node_000000已被刪除,而此時node_000001通過watcher監聽發現自己為為最小的臨時節點,所以獲取鎖成功。

異常場景分析

使用者端1建立臨時節點後,會與Zookeeper伺服器維護一個Session,這個Session會依賴使用者端 定時心跳來維持連線。由於網路異常原因,Zookeeper長時間收不到使用者端1的心跳,就認為這個Session過期了,也會把這個臨時節點刪除,此時使用者端2建立臨時節點能夠獲取鎖成功。當用戶端網路恢復正常後,它仍然認為持有鎖,此時就會造成鎖衝突。

具體實現

Zookeeper實現分散式鎖,可以採用Curator實現分散式鎖,關於SpringBoot如何整合Curator,大家可以參考如下文章:

Java Spring Boot 整合Zookeeper

Zookpeer實現分散式鎖實現庫存扣減

 @RequestMapping("/lockStock")
    public void lockStock()
    {
       zooKeeperUtil.lock("/Locks", 1000, TimeUnit.SECONDS, ()->{
           //業務邏輯
       });
    }

小結:

關於分散式鎖的實現的對比,詳情請檢視下圖:

總結

本文詳細的介紹了幾種分散式鎖的實現和使用,業務需要根據場景選擇合適的分散式鎖的實現,如有疑問,請隨時反饋。

到此這篇關於Redis常見分佈鎖的原理和實現的文章就介紹到這了,更多相關Redis分佈鎖原理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com