1. 程式人生 > >Java實現Redis分散式鎖

Java實現Redis分散式鎖

1、背景:

在多執行緒環境下,通常會使用鎖來保證有且只有一個執行緒來操作共享資源。比如:

object obj = new object();
lock (obj) 

//操作共享資源 
}

利用作業系統提供的鎖機制,可以確保多執行緒或多程序下的併發唯一操作。但如果在多機環境下就不能滿足了,當A,B兩臺機器同時操作C機器的共享資源時,就需要第三方的鎖機制來保證在分散式環境下的資源協調,也稱分散式鎖。

在分散式環境下(多節點主機)加鎖處理場景,如:秒殺、全域性遞增ID等等,需要對資源(變數)同步互斥。大部分的解決方案是基於DB實現的,Redis為單程序單執行緒模式,採用佇列模式將併發訪問變成序列訪問,且多客戶端對Redis的連線並不存在競爭關係。由於Redis是單執行緒模型,命令操作原子性,所以利用這個特性可以很容易的實現分散式鎖。


2、Redis有三個最基本屬性來保證分散式鎖的有效實現:

安全性: 互斥,在任何時候,只有一個客戶端能持有鎖。
活躍性A:沒有死鎖,即使客戶端在持有鎖的時候崩潰,最後也會有其他客戶端能獲得鎖,超時機制。

活躍性B:故障容忍,只有大多數Redis節點時存活的,客戶端仍可以獲得鎖和釋放鎖。

使用Redis實現分散式鎖,有兩個重要函式需要介紹

1)SETNX命令(SET if Not eXists)
語法:
SETNX key value
功能:
當且僅當 key 不存在,將 key 的值設為 value ,並返回1;若給定的 key 已經存在,則 SETNX 不做任何動作,並返回0。


2)GETSET命令
語法:
GETSET key value
功能:
將給定 key 的值設為 value ,並返回 key 的舊值 (old value),當 key 存在但不是字串型別時,返回一個錯誤,當key不存在時,返回nil。

3、程式碼:

  1. package ct.tool;  
  2. import redis.clients.jedis.Jedis;  
  3. publicclass RedisDisLock {  
  4.     privatestaticfinallong expired = 1000;//1秒超時
  5.     //上鎖
  6.     publicstaticboolean acquireLock(Jedis jedis,String lock) {  
  7.         // 1. 通過SETNX試圖獲取一個lock
  8.         boolean success = false;  
  9.         long value = System.currentTimeMillis() + expired + 
    1;        
  10.         long acquired = jedis.setnx(lock, String.valueOf(value));  
  11.         jedis.expire(lock, 1);//設定1秒超時
  12.         //SETNX成功,則成功獲取一個鎖
  13.         if (acquired == 1)  success = true;  
  14.         //SETNX失敗,說明鎖被其他客戶端保持,檢查其是否已經超時
  15.         /*else { 
  16.             long oldValue = Long.valueOf(jedis.get(lock));          
  17.             if (oldValue < System.currentTimeMillis()) {//超時 
  18.                 //獲取上一個鎖到期時間,並設定現在的鎖到期時間, 
  19.                 //只有一個執行緒才能獲取上一個線上的設定時間,因為jedis.getSet是同步的 
  20.                 String getValue = jedis.getSet(lock, String.valueOf(value)); 
  21.                 if (getValue !=null) { 
  22.                     if (Long.valueOf(getValue) == oldValue)  
  23.                         success = true;  
  24.                     else success = false;// 已被其他程序捷足先登了 
  25.                 }             
  26.             }else //未超時,則直接返回失敗 
  27.                 success = false; 
  28.         }        */
  29.         return success;        
  30.     }  
  31.     //釋放鎖
  32.     publicstaticvoid releaseLock(Jedis jedis,String lock) {      
  33.         //long current = System.currentTimeMillis();       
  34.         // 避免刪除非自己獲取得到的鎖
  35.         //if (current < Long.valueOf(jedis.get(lock)))
  36.             jedis.del(lock);   
  37.     }  
  38. }  


程式碼應用中,要共享的程式碼段前加:
  1. //枷鎖
  2.             boolean lockFlag=true;  
  3.             while(lockFlag){//迴圈等待拿鎖
  4.                 if (RedisDisLock.acquireLock(jd,"o2o")) lockFlag=false;  
  5.             }  

業務處理後,釋放:
  1. RedisDisLock.releaseLock(jd, "o2o");