什麼是分佈式鎖及正確使用redis實現分佈式鎖 什麼是分佈式鎖及正確使用redis實現分佈式鎖

 

轉:html

什麼是分佈式鎖及正確使用redis實現分佈式鎖

 分佈式鎖

  分佈式鎖其實能夠理解爲:控制分佈式系統有序的去對共享資源進行操做,經過互斥來保持一致性。 舉個不太恰當的例子:假設共享的資源就是一個房子,裏面有各類書,分佈式系統就是要進屋看書的人,分佈式鎖就是保證這個房子只有一個門而且一次只有一我的能夠進,並且門只有一把鑰匙。而後許多人要去看書,能夠,排隊,第一我的拿着鑰匙把門打開進屋看書而且把門鎖上,而後第二我的沒有鑰匙,那就等着,等第一個出來,而後你在拿着鑰匙進去,而後就是以此類推java

 實現原理

  • 互斥性redis

    • 保證同一時間只有一個客戶端能夠拿到鎖,也就是能夠對共享資源進行操做數據庫

  • 安全性安全

    • 只有加鎖的服務纔能有解鎖權限,也就是不能讓a加的鎖,bcd均可以解鎖,若是都能解鎖那分佈式鎖就沒啥意義了分佈式

    • 可能出現的狀況就是a去查詢發現持有鎖,就在準備解鎖,這時候突然a持有的鎖過時了,而後b去得到鎖,由於a鎖過時,b拿到鎖,這時候a繼續執行第二步進行解鎖若是不加校驗,就將b持有的鎖就給刪除了post

  • 避免死鎖lua

    • 出現死鎖就會致使後續的任何服務都拿不到鎖,不能再對共享資源進行任何操做了url

  • 保證加鎖與解鎖操做是原子性操做spa

    • 這個其實屬因而實現分佈式鎖的問題,假設a用redis實現分佈式鎖
    • 假設加鎖操做,操做步驟分爲兩步:

    • 1,設置key set(key,value)2,給key設置過時時間

    • 假設如今a剛實現set後,程序崩了就致使了沒給key設置過時時間就致使key一直存在就發生了死鎖

 如何實現分佈式鎖

  實現分佈式鎖的方式有不少,只要知足上述條件的均可以實現分佈式鎖,好比數據庫,redis,zookeeper,在這裏就先講一下如何使用redis實現分佈式鎖

 使用redis實現分佈式鎖

  • 使用redis命令 set key value NX EX max-lock-time 實現加鎖

  • 使用redis命令 EVAL 實現解鎖

 

加鎖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Jedis jedis = new Jedis( "127.0.0.1" , 6379 );
 
  private static final String SUCCESS = "OK" ;
 
 
 
  /**
 
   * 加鎖操做
 
   * @param key 鎖標識
 
   * @param value 客戶端標識
 
   * @param timeOut 過時時間
 
   */
 
  public Boolean lock(String key,String value,Long timeOut){
 
      String var1 = jedis.set(key,value, "NX" , "EX" ,timeOut);
 
      if (LOCK_SUCCESS.equals(var1)){
 
          return true ;
 
      }
 
      return false ;
 
  }

解讀:

  • 加鎖操做:jedis.set(key,value,"NX","EX",timeOut)【保證加鎖的原子操做】

  • key就是redis的key值做爲鎖的標識,value在這裏做爲客戶端的標識,只有key-value都比配纔有刪除鎖的權利【保證安全性】

  • 經過timeOut設置過時時間保證不會出現死鎖【避免死鎖】

  • NX,EX什麼意思?

    • NX:只有這個key不存才的時候纔會進行操做,if not exists;

    • EX:設置key的過時時間爲秒,具體時間由第5個參數決定


解鎖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Jedis jedis = new Jedis( "127.0.0.1" , 6379 );
 
  private static final Long UNLOCK_SUCCESS = 1L;
 
 
 
  /**
 
   * 解鎖操做
 
   * @param key 鎖標識
 
   * @param value 客戶端標識
 
   * @return
 
   */
 
  public static Boolean unLock(String key,String value){
 
      String luaScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else  return 0 end" ;
 
      Object var2 = jedis.eval(luaScript,Collections.singletonList(key), Collections.singletonList(value));
 
      if (UNLOCK_SUCCESS == var2) {
 
          return true ;
 
      }
 
      return false ;
 
  }

解讀:

  • luaScript 這個字符串是個lua腳本,表明的意思是若是根據key拿到的value跟傳入的value相同就執行del,不然就返回0【保證安全性】

  • jedis.eval(String,list,list);這個命令就是去執行lua腳本,KEYS的集合就是第二個參數,ARGV的集合就是第三參數【保證解鎖的原子操做】

上述就實現了怎麼使用redis去正確的實現分佈式鎖,可是有個小缺陷就是鎖過時時間要設置爲多少合適,這個其實仍是須要去根據業務場景考量一下的


  上面那只是講了加鎖與解鎖的操做,試想一下若是在業務中去拿鎖若是沒有拿到是應該阻塞着一直等待仍是直接返回,這個問題其實能夠寫一個重試機制,根據重試次數和重試時間作一個循環去拿鎖,固然這個重試的次數和時間設多少合適,是須要根據自身業務去衡量的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
 
  * 重試機制
 
  * @param key 鎖標識
 
  * @param value 客戶端標識
 
  * @param timeOut 過時時間
 
  * @param retry 重試次數
 
  * @param sleepTime 重試間隔時間
 
  * @return
 
  */
 
public Boolean lockRetry(String key,String value,Long timeOut,Integer retry,Long sleepTime){
 
     Boolean flag = false ;
 
     try {
 
         for ( int i= 0 ;i<retry;i++){
 
             flag = lock(key,value,timeOut);
 
             if (flag){
 
                 break ;
 
             }
 
             Thread.sleep(sleepTime);
 
         }
 
     } catch (Exception e){
 
         e.printStackTrace();
 
     }
 
     return flag;
 
}

  

到這,用redis實現分佈式鎖就寫完了,下次用zookeeper去實現分佈式鎖,歡迎留言吐槽 txtx

 

文中set命令詳解:http://redisdoc.com/string/set.html

相關文章
相關標籤/搜索