分布式-技術專區-Redis分布式鎖原理實現


  在很多場景中,我們為了保證數據的最終一致性,需要很多的技術方案來支持,比如分布式事務、分布式鎖等。那具體什么是分布式鎖,分布式鎖應用在哪些業務場景、如何來實現分布式鎖呢?今天來探討分布式鎖這個話題。

什么是分布式鎖

要介紹分布式鎖,首先要提到與分布式鎖相對應的是線程鎖、進程鎖。

1.線程鎖

  主要用來給方法、代碼塊加鎖。當某個方法或代碼使用鎖,在同一時刻僅有一個線程執行該方法或該代碼段。線程鎖只在同一JVM中有效果,因為線程鎖的實現在根本上是依靠線程之間共享內存實現的,比如Synchronized、Lock等。

2.進程鎖

  為了控制同一操作系統中多個進程訪問某個共享資源,因為進程具有獨立性,各個進程無法訪問其他進程的資源,因此無法通過synchronized等線程鎖實現進程鎖。

3.分布式鎖

  當多個進程不在同一個系統中,用分布式鎖控制多個進程對資源的訪問。

分布式鎖的由來

  在傳統單機部署的情況下,可以使用Java並發處理相關的API(如ReentrantLcok或synchronized)進行互斥控制。

  但是在分布式系統后,由於分布式系統多線程、多進程並且分布在不同機器上,這將使原單機並發控制鎖策略失效,為了解決這個問題就需要一種跨JVM的互斥機制來控制共享資源的訪問,這就是分布式鎖的由來。

  當多個進程不在同一個系統中,就需要用分布式鎖控制多個進程對資源的訪問。

分布式鎖的特點

首先,為了確保分布式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件:

  1、互斥性:任意時刻,只能有一個客戶端獲取鎖,不能同時有兩個客戶端獲取到鎖。

  2、安全性:鎖只能被持有該鎖的客戶端刪除,不能由其它客戶端刪除。

  3、死鎖:獲取鎖的客戶端因為某些原因(如down機等)而未能釋放鎖,其它客戶端再也無法獲取到該鎖。

  4、容錯:當部分節點(redis節點等)down機時,客戶端仍然能夠獲取鎖和釋放鎖。

分布式鎖的具體實現

分布式鎖一般有三種實現方式:

  1. 數據庫樂觀鎖;
  2. 基於ZooKeeper的分布式鎖;
  3. 基於Redis的分布式鎖;

Redis實現分布式鎖

  基於Redis命令:SET key value NX EX max-lock-time

  這里補充下: 從2.6.12版本后, 就可以使用set來獲取鎖, Lua 腳本來釋放鎖。setnx是老黃歷了,set命令nx,xx等參數, 是為了實現 setnx 的功能。

1.加鎖

  public class RedisTool {

    private static final String LOCK_SUCCESS = “OK”;

    private static final String SET_IF_NOT_EXIST = “NX”;

    private static final String SET_WITH_EXPIRE_TIME = “PX”;

    /* 嘗試獲取分布式鎖 

    @param jedis Redis客戶端 

    @param lockKey 鎖 

    @param requestId 請求標識 

    @param expireTime 超期時間 

    @return 是否獲取成功 

    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

       String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

      if (LOCK_SUCCESS.equals(result)) {

        return true;

      }

      return false;

     }

    }

  jedis.set(String key, String value, String nxxx, String expx, int time)

  這個set()方法一共有五個形參:

  第一個為key,我們使用key來當鎖,因為key是唯一的。

  第二個為value,我們傳的是requestId,很多童鞋可能不明白,有key作為鎖不就夠了嗎,為什么還要用到value?原因就是我們在上面講到可靠性時,分布式鎖要滿足第四個條件解鈴還須系鈴人,通過給value賦值為requestId,我們就知道這把鎖是哪個請求加的了,在解鎖的時候就可以有依據。requestId可以使用UUID.randomUUID().toString()方法生成。

  第三個為nxxx,這個參數我們填的是NX,意思是SET IF NOT EXIST,即當key不存在時,我們進行set操作;若key已經存在,則不做任何操作;

  第四個為expx,這個參數我們傳的是PX,意思是我們要給這個key加一個過期的設置,具體時間由第五個參數決定。

  第五個為time,與第四個參數相呼應,代表key的過期時間。

  總的來說,執行上面的set()方法就只會導致兩種結果:

  1. 當前沒有鎖(key不存在),那么就進行加鎖操作,並對鎖設置個有效期,同時value表示加鎖的客戶端。

  2. 已有鎖存在,不做任何操作。

2.解鎖

  public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;

    /* 釋放分布式鎖 

    @param jedis Redis客戶端 

    @param lockKey 鎖 

    @param requestId 請求標識 

    @return 是否釋放成功

    */

   public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

    String script = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;

    Object result = jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(requestId));

    if (RELEASE_SUCCESS.equals(result)) {

      return true;

    }

    return false;

    }

  }

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM