Redisson框架實現Redis分佈式鎖的實現原理

一、前言

先看一段Redisson框架調用

RLock lock = redisson.getLock("myLock");
lock.lock();
//.......業務代碼
lock.unlock();

Redisson支持redis單實例、redis哨兵、redis cluster、redis master-slave等各種部署架構

二、Redisson實現redis分佈式鎖的底層原理

1、加鎖機制

以redis cluster集羣爲例,現在某個客戶端要加鎖,它首先會hash節點選擇一臺機器。(這裏只是選擇一臺機器,還沒有加鎖),接着,會發送一段lua腳本到redis上,腳本如下:(用lua腳本的原因:就是爲了保證下面指令的原子性)

//判斷KEYS[1]是否存在,如果不存在,說明還沒有客戶端獲取鎖
"if(redis.call('exists',KEYS[1])==0) then "+ 
      //key不存在,那麼用hset命令加鎖,field爲:客戶端id value爲:1(客戶端獲取了一次鎖)
     "redis.call('hset',KEYS[1],ARGV[2],1); "+ 
     //設置這個鎖的過期時長爲 ARGV[1]
     "redis.call('pexpire',KEYS[1],ARGV[1]); "+
     //返回
     "return nil; "+
//if結束     
"end; "+
//如果KEYS[1]已經存在,那麼再判斷field爲ARGV[2]的值是否存在,即是:判斷客戶端ARGV[2]是否獲得了鎖
"if(redis.call('hexists',KEYS[1],ARGV[2])==1) then "+
     //如果客戶端獲取了鎖,那麼將field的值加1,即是:將對應客戶端獲取鎖的次數加1
     "redis.call('hincrby',KEYS[1],ARGV[2],1); "+
     //設置這個key下field值過期時長
     "redis.call('pexpire',KEYS[1],ARGV[1]); "+
     //返回
     "return nil; "+
//if結束     
"end; "+
//key已經存在,並且獲取鎖的客戶端id不是當前執行方法的客戶端id,返回鎖key的剩餘生存時間
//此時客戶端2會進入一個while循環,不停的嘗試加鎖。
"return redis.call('pttl',KEYS[1]);";

參數:

  • KEYS[1]:代表的是你加鎖的那個key
    比如:RLock lock = redisson.getLock("myLock");
  • ARGV[1]:代表的就是鎖key的默認生存時間,默認30秒。
  • ARGV[2]:代表的是加鎖的客戶端的ID,類似於下面這樣:
    34634f3f3b-2342-3244-87fd-34234efdsf3423f34f:1

首先用exists myLock命令判斷,如果要加的鎖key不存在,
那麼就用hset myLock 34634f3f3b-2342-3244-87fd-34234efdsf3423f34f:1 1 命令加鎖,設置key爲myLock,field爲客戶端id ,值爲1 的hash值;接着執行pexpire myLock 30000,設置myLock鎖 key的生存時間是30秒。

2、鎖的互斥

如果客戶2現在也來嘗試加鎖:
判斷exists myLock已經存在,那麼執行第二個If 塊,判斷myLock鎖key的hash數據結構中,是否包含客戶端2的ID,但是明顯不是的,因爲那裏包含的是客戶端1的ID。
所以,客戶端2會執行pttl myLock,獲取myLock這個鎖key的剩餘生存時間。比如還剩10000ms,此時客戶端2進入一個while循環,不停的嘗試加鎖。

3、watch dog自動延期機制

客戶端1加鎖的鎖key默認生存時間30秒,如果超過了30秒,客戶端1還想一直持有這把鎖,怎麼辦呢?

watch dog自動延期機制:只要客戶端1一旦加鎖成功,就會啓動一個watch dog看門狗,他是一個後臺線程,會每隔10秒檢查一下,如果客戶端1還持有鎖key,那麼就會不斷的延長鎖key的生存時間。

4、可重入加鎖機制

客戶端如果已經持有了鎖,如果此時 再嘗試獲取鎖 會怎樣?比如:

RLock lock = redisson.getLock("myLock");
lock.lock();
//.....
lock.lock();
//.....
lock.unlock();
lock.unlock();

第一個if判斷肯定不成立,“exists myLock”會顯示鎖key已經存在了。
第二個if判斷會成立,因爲myLock的hash數據結構中包含的那個ID,就是客戶端1的那個ID,此時就會執行可重入鎖的邏輯incrby myLock 34634f3f3b-2342-3244-87fd-34234efdsf3423f34f:1 1,將field爲客戶端1的值加1,變成2。就是將獲取鎖的次數變成了2

5、鎖的釋放機制

執行lock.unlock(),就可以釋放分佈式鎖,實際上就是每次都對myLock數據結構中的那個加鎖次數減1。如果發現加鎖次數是0了,說明這個客戶端已經不再持有鎖了,此時就會用:
del myLock命令,從redis裏刪除這個key。
其它的客戶端就可以嘗試完成加鎖

6、Redisson實現Redis分佈式鎖的缺點

上面方案最大的問題就是:如果你對某個redis master實例,寫入了myLock這種鎖key的value,此時會異步複製給對應的master slave實例。
但是這個過程中一旦發生redis master宕機,主備切換,redis slave變爲了redis master。
接着就會導致,客戶端2來嘗試加鎖的時候,在新的redis master上完成了加鎖,而客戶端1也以爲自己成功加了鎖。
此時就會導致多個客戶端對一個分佈式鎖完成了加鎖。
這時系統在業務語義上一定會出現問題,導致各種髒數據的產生。
所以這個就是redis cluster,或者是redis master-slave架構的主從異步複製導致的redis分佈式鎖的最大缺陷:在redis master實例宕機的時候,可能導致多個客戶端同時完成加鎖。

7、總結

根據上面解析,那麼可總結出一張redisson獲取鎖的流程圖如下:
在這裏插入圖片描述

參考:
https://mp.weixin.qq.com/s?__biz=MzU0OTk3ODQ3Ng==&mid=2247483893&idx=1&sn=32e7051116ab60e41f72e6c6e29876d9&chksm=fba6e9f6ccd160e0c9fa2ce4ea1051891482a95b1483a63d89d71b15b33afcdc1f2bec17c03c&scene=21#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章