1. 程式人生 > >5、redis分散式鎖

5、redis分散式鎖

開發十年,就只剩下這套架構體系了! >>>   

參考連結:https://www.cnblogs.com/linjiqin/p/8003838.html

一:介紹

實現分散式鎖有三種方式:1、資料庫樂觀鎖,2、基於redis,3、基於zookeeper。 redis服務端是單執行緒操作,完美地避免了多執行緒同步問題,實現了原子性。

首先,為了確保分散式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件: 互斥性。在任意時刻,只有一個客戶端能持有鎖。

不會發生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其他客戶端能加鎖。

具有容錯性。只要大部分的Redis節點正常執行,客戶端就可以加鎖和解鎖。

解鈴還須繫鈴人。加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。

二:實現

1、加鎖

  • 1、新版本jedis(2.2.0及以上),set支援判斷是否存在,並且設定過期時間

      // 2.2.0及以上版本set支援多引數設定,實現存在判斷以及過期時間設定功能
      String result = jedis.set("lockKey", "requesId", "NX", "PX", 20000);
      if ("OK".equals(result)){
          return true;
      }
      return false;
    
  • 2、舊版本jedis,使用setnx和expire結合使用 ==> 有缺陷

      Long result = jedis.setnx("lockKey", "App1Lock");
      if (result == 1) {
          // 若在這裡程式突然崩潰,則無法設定過期時間,將發生死鎖
          jedis.expire("lockKey", 10000);
      }
    
  • 3、使用Lua指令碼 對 舊版jedis改進。

      eval命令執行Lua程式碼的時候,Lua程式碼將被當成一個命令去執行,並且直到eval命令執行完成,Redis才會執行其他命令。所以整體具有原子性
    
      // KEYS是setnxMsg的值,ARGV是expireMsg的值,其實可以在setnxMsg中設定所有的引數,裡面用KEYS[]來取
      String script = "if redis.call('setnx', KEYS[1], KEYS[2]) == 1 then return redis.call('expire', ARGV[1], ARGV[2]) else return 0 end";
      LinkedList<String> setnxMsg = new LinkedList<>();
      LinkedList<String> expireMsg = new LinkedList<>();
      setnxMsg.add("lockKey");
      setnxMsg.add("app-1-lock");
      expireMsg.add("lockKey");
      expireMsg.add("20"); // 這個是秒
    
      Object result = jedis.eval(script, setnxMsg, expireMsg);
      if ("1".equals(result)) {
          return true;
      }
      return false;
    

2、解鎖

  • 1、直接del的問題 ==> 任何人都可以刪除,這不可以

      jedis.del("lockKey");
    
  • 2、雖然做了判斷,但是分開執行,還是會有bug

      if ("app-1-lockKey".equals(jedis.get("lockKey"))){
          // 此時剛好key過期,其他app進行鎖獲取,則會刪除別人的
          jedis.del("lockKey");
      }
    
  • 3、最可靠的解鎖:lua表示式將判斷和刪除整合在一起執行

      String script = "if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end";
      LinkedList<String> msgList = new LinkedList<>();
      msgList.add("lockKey");
      msgList.add("app-1-lock");
    
      Object result = jedis.eval(script, msgList, new LinkedList<>()); // 最後引數不能null
    
      if ("1".equals(result)) {
          return true;
      }
      return false;