Redis基礎知識(學習筆記22--分散式鎖 Redisson )

东山絮柳仔發表於2024-09-23

分散式鎖是控制分散式系統間同步訪問共享資源的一種方式,其可以保證共享資源在併發場景下的資料一致性。

1. 工作原理

當有多個執行緒要訪問某一個共享資源(DBMS中的資料或Redis中的資料,或共享檔案等)時,為了達到協調多個執行緒的同步訪問,此時就需要使用分散式鎖了。

為了達到同步訪問的目的,規定:讓這些執行緒在訪問共享資源之前先要獲得一個令牌token,只有具有令牌的執行緒才可以訪問共享資源。這個令牌就是透過各種技術實現的分散式鎖。而這個分散式鎖是一種”互斥資源“,即只有一個。只要有執行緒搶到了鎖,那麼其它執行緒只能等待,直到鎖被釋放或等待超時。

2. Redisson

2.1 原理

Redisson 內部使用Lua指令碼實現了對可重入鎖的新增、重入、續約(續命)、釋放。Redisson需要使用者為鎖指定一個key,但無需為鎖指定過期時間,因為它有預設過期時間(當然,也可指定)。由於該鎖具有“可重入”功能,所以Redisson會為該鎖生成一個計數器,記錄一個執行緒重入鎖的次數。

2.2. 加鎖與釋放鎖

其核心是透過Lua指令碼實現

加鎖的Lua指令碼

//exists',KEYS[1])==0 不存在,沒鎖
"if (redis.call('exists',KEYS[1])==0) then "+       --看有沒有鎖
  // 命令:hset,1:第一回
    "redis.call('hset',KEYS[1],ARGV[2],1) ; "+       --無鎖 加鎖 
    // 配置鎖的生命週期 
    "redis.call('pexpire',KEYS[1],ARGV[1]) ; "+      
    "return nil; end ;" +

//可重入操作,判斷是不是我加的鎖
"if (redis.call('hexists',KEYS[1],ARGV[2]) ==1 ) then "+  --我加的鎖
   //hincrby 在原來的鎖上加1
    "redis.call('hincrby',KEYS[1],ARGV[2],1) ; "+  --重入鎖
    "redis.call('pexpire',KEYS[1],ARGV[1]) ; "+  
    "return nil; end ;" +

//否則,鎖存在,返回鎖的有效期,決定下次執行指令碼時間
"return redis.call('pttl',KEYS[1]) ;"  --不能加鎖,返回鎖的時間

lua的作用:保證這段複雜業務邏輯執行的原子性。

lua的解釋:

  • KEYS[1]) : 加鎖的key
  • ARGV[1] : key的生存時間,預設為30秒
  • ARGV[2] : 加鎖的客戶端ID (UUID.randomUUID()) + “:” + threadId)

釋放鎖的lua指令碼

# 如果key已經不存在,說明已經被解鎖,直接釋出(publish)redis訊息(無鎖,直接返回)
"if (redis.call('exists', KEYS[1]) == 0) then " +
            "redis.call('publish', KEYS[2], ARGV[1]); " +
            "return 1; " +
          "end;" +
# key和field不匹配,說明當前客戶端執行緒沒有持有鎖,不能主動解鎖。 不是我加的鎖 不能解鎖 (有鎖不是我加的,返回)
          "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
            "return nil;" +
          "end; " +
# 將value減1 (有鎖是我加的,進行hincrby -1"local counter = redis.call('hincrby', KEYS[1], ARGV[3],-1); " +
# 如果counter>0說明鎖在重入,不能刪除key
          "if (counter > 0) then " +
            "redis.call('pexpire', KEYS[1], ARGV[2]); " +
            "return 0; " +
# 刪除key並且publish 解鎖訊息
# 可重入鎖減完了,進行del操作
          "else " + 
            "redis.call('del', KEYS[1]); " + #刪除鎖
            "redis.call('publish', KEYS[2], ARGV[1]); " +
            "return 1; "+
             "end; " +
             "return nil;",
  • – KEYS[1] :需要加鎖的key,這裡需要是字串型別。
  • – KEYS[2] :redis訊息的ChannelName,一個分散式鎖對應唯一的一個channelName: “redisson_lockchannel{” + getName() + “}”
  • – ARGV[1] :reids訊息體,這裡只需要一個位元組的標記就可以,主要標記redis的key已經解鎖,再結合 redis的Subscribe,能喚醒其他訂閱解鎖訊息的客戶端執行緒申請鎖。
  • – ARGV[2] :鎖的超時時間,防止死鎖
  • – ARGV[3] :鎖的唯一標識,也就是剛才介紹的 id(UUID.randomUUID()) + “:” + threadId

2.3 網址

https://github.com/redisson/redisson

知識wiki

https://github.com/redisson/redisson/wiki/Table-of-Content

3. Redisson 常用鎖

3.1 可重入鎖

Redisson的分散式鎖RLock是一種可重入鎖。當一個執行緒獲取到鎖之後,這個執行緒可以再次獲取本物件上的鎖,而其他的執行緒是不可以的。

  • JDK中的reentrantLock(entrant是進入者、新會員的意思,reentrant是 可重入、可重入的 的意思)是可重入鎖,其是透過AQS(抽象物件同步器)實現的鎖機制。
  • synchronized也是可重入鎖,其是透過監視器模式(本質是OS的互斥鎖)實現的鎖機制。

3.2 公平鎖 Fair Lock

Redisson的可重入鎖RLock預設是一種非公平鎖,但也支援可重入公平鎖 Fair Lock。當有多個執行緒同時申請鎖時,這些執行緒會進入到一個FIFO佇列,只有隊首元素才會獲取到鎖,其它元素等待。只有當鎖被釋放後,才會再將鎖分配給當前的隊首元素。

3.3 聯鎖 MultiLock

Redisson 分散式鎖可以實現聯鎖 MultiLock。當一個執行緒需要同時處理多個共享資源時,可使用聯鎖。即一次性申請多個鎖,同時鎖定多個共享資源。聯鎖可以預防死鎖。相當於對共享資源的申請實現了原子性:要麼都申請到,只要缺少一個資源,則將申請到的資源釋放。其是OS底層原理中 AND 型訊號量機制的典型應用。

3.4 紅鎖 RedLock

Redisson 分散式鎖可以實現紅鎖 RedLock。紅鎖由多個鎖(同時至少是向三個Redis叢集申請的鎖)構成,只有當這些鎖中的大部分鎖申請成功時,紅鎖才申請成功。紅鎖一般用於解決Redis主從叢集鎖丟失問題。

紅鎖和聯鎖的區別:紅鎖實現的是對同一個共享資源的同步訪問控制,而聯鎖實現的是多個共享資源的同步訪問控制。

3.5 讀寫鎖 RReadWriteLock

透過Redisson可以獲取到讀寫鎖 RReadWriteLock。透過 RReadWriteLock 例項可分別獲取到讀鎖 RedissonReadLock 和 寫鎖 RedissonWriteLock。讀鎖與寫鎖分別是實現了RLock的可重入鎖。

一個共享資源,在沒有寫鎖的情況下,允許同時新增多個讀鎖。只要新增了寫鎖,任何讀鎖與寫鎖都不可以再次新增。即讀鎖是共享資源,寫鎖是排他鎖。

3.6 訊號量 Semaphore

透過Redisson可以獲取到訊號量RSemaphore。RSemaphore的常用場景有兩種:一種是,無論誰新增的鎖,任何其它執行緒都可以解鎖,就可以使用RSemaphore。另外,當一個執行緒需要一次申請多個資源時,可使用RSemaphore。RSemaphore是訊號量機制的典型應用。

3.7 可過期性訊號量 PermitExpirableSemaphore

透過Redisson可以獲取到可過期訊號量PermitExpirableSemaphore。該訊號量是在RSemaphore基礎上,為每個訊號量增加了一個過期時間,且每個訊號都可以透過獨立的ID來辨識。釋放時也只能透過提交該ID才能釋放。

不過,一個執行緒每次只能申請一個訊號量,當然每次也只會釋放一個訊號量。這是與RSemaphore不同的地方。

該訊號量為互斥訊號量時,其就等同於可重入鎖。或者說,可重入鎖就相當於訊號量為1的可過期訊號量。

可過期訊號量與可重入鎖的區別,可重入鎖:相當於使用者每次只能申請1個訊號量,且只有一個使用者可以申請成功。可過期訊號量:使用者每次只能申請1個訊號量,但可以有多個使用者申請成功。

3.8 閉鎖 RCountDownLatch

透過Redisson 可以獲取到分散式閉鎖 RCountDownLatch,其與JDK的JUC中的閉鎖CountDownLatch原理一樣,用法類似。其常用於一個或多個執行緒的執行必須在其它某些任務執行完畢的場景。例如,大規模分散式平行計算中,最終的合併計算必須基於很多平行計算的執行完畢。

閉鎖中定義了一個計數器和一個阻塞佇列。阻塞佇列中存放者待執行的執行緒(又稱 合併執行緒)。每當一個並行任務執行完畢(又稱 條件執行緒),計數器就減1.當計數器遞減到0時就會喚醒阻塞佇列中的所有執行緒。

如果不使用Redisson,那麼通常使用Barrier佇列解決該問題,而Barrier佇列通常使用Zookeeprt實現。

學習筆記--參閱特別宣告

1.【Redis影片從入門到高階】

【https://www.bilibili.com/video/BV1U24y1y7jF?p=11&vd_source=0e347fbc6c2b049143afaa5a15abfc1c】

2.《Redis:Redisson分散式鎖的使用方式(推薦使用)》

https://www.jb51.net/database/319949d8d.htm

相關文章