posts - 22, comments - 32, trackbacks - 0, articles - 73
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          分布式鎖實現(xiàn)-redis,zk

          Posted on 2021-03-24 20:11 為自己代言 閱讀(210) 評論(0)  編輯  收藏 所屬分類: java/J2EE
          1:分布鎖有好多實現(xiàn)方式
          •  基于數(shù)據(jù)庫實現(xiàn)
                這個實現(xiàn)方式比較復(fù)雜,考慮因素比較多,比如:超時,非公平鎖,非重入等會有各種各樣的問題,在解決問題的過程中會使整個方案變得越來越復(fù)雜。操作數(shù)據(jù)庫需要一定的開銷,性能問題需要考慮      
          • 基于redis實現(xiàn)(這個對于不太敏感的場景可以使用,由于redis集群和單機(jī),還有客戶端,版本等多方面因素考慮情況比較多)
                 性能好。使用緩存實現(xiàn)分布式鎖的缺點 其數(shù)據(jù)庫一樣
          • 基于zookeeper實現(xiàn)(這個是最終也是最好最可靠的)
                 創(chuàng)建臨時節(jié)點,可以解決單機(jī),鎖無法釋放,非阻塞,不可沖入,非公平的問題
           
              總結(jié)
          從理解的難易程度角度(從低到高)

          數(shù)據(jù)庫 > 緩存 > Zookeeper

          從實現(xiàn)的復(fù)雜性角度(從低到高)

          Zookeeper > 緩存 > 數(shù)據(jù)庫

          從性能角度(從高到低)

          緩存 > Zookeeper >= 數(shù)據(jù)庫

          從可靠性角度(從高到低)

          Zookeeper > 緩存 > 數(shù)據(jù)庫
          下面講基于redis實現(xiàn)分布鎖代碼:RedisTemplate 客戶端 lettuce


          @Service
          public class RedisDistributedLockUtils {

              @Autowired
              
          private RedisTemplate redisTemplate;

              
          private static final Long RELEASE_SUCCESS = 1L;

              
          private static final long DEFAULT_TIMEOUT = 1000 * 10;
              
          //因為要使用lua 腳本是因為 redis 執(zhí)行l(wèi)ua腳本是原子操作
              private static final String UNLOCK_LUA= "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

              
          /**
               * 實時獲取鎖
               *
               * 嘗試獲取分布式鎖 將redis版本升級到2.1以上(spring-boot-starter-data-redis 版本 2.X以上),然后使用setIfAbsent 不存在
               * 當(dāng)setIfAbsent成功之后斷開連接,下面設(shè)置過期時間的代碼 stringRedisTemplate.expire(key,timeout);是無法執(zhí)行的,這時候就會有大量沒有過期時間的數(shù)據(jù)存在數(shù)據(jù)庫
               * 
          @param lockKey    鎖
               * 
          @param requestId  請求標(biāo)識
               * 
          @param expireTime 超期時間
               * 
          @return 是否獲取成功
               
          */
              
          public boolean trySetDistributedLock(String lockKey, String requestId, long expireTime) {
                  
          return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId,0 == expireTime ? DEFAULT_TIMEOUT : expireTime, TimeUnit.MILLISECONDS);
              }

              
          /**
               * 以阻塞方式的獲取鎖
               * 
          @param key
               * 
          @param value
               * 
          @param timeout
               * 
          @return
               
          */
              
          public boolean setDistributedLock(String key, String value, long timeout) {
                  Boolean lock 
          = false;
                  
          long start = System.currentTimeMillis();
                  
          while (!lock && (System.currentTimeMillis() - start < timeout)) {
                      
          //執(zhí)行set命令
                      lock = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);
                      
          //不頻繁去獲取鎖
                      try {
                          
          if (!lock) {
                              Thread.sleep(
          60);
                          }
                      } 
          catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
                  
          return lock;
              }

              
          public boolean releaseLock(String key, String value) {
                  
          // 使用Lua腳本:先判斷是否是自己設(shè)置的鎖,再執(zhí)行刪除
                  
          // 使用lua腳本刪除redis中匹配value的key,可以避免由于方法執(zhí)行時間過長而redis鎖自動過期失效的時候誤刪其他線程的鎖
                  
          // spring自帶的執(zhí)行腳本方法中,集群模式直接拋出不支持執(zhí)行腳本的異常EvalSha is not supported in cluster environment
                  
          // 所以只能拿到原redis的connection來執(zhí)行腳本

                  List
          <String> keys = new ArrayList<>();
                  keys.add(key);
                  List
          <String> args = new ArrayList<>();
                  args.add(value);
                  Long result 
          = (Long)redisTemplate.execute(new RedisCallback<Long>() {
                      @Override
                      
          public Long doInRedis(RedisConnection connection) throws DataAccessException {
                          Object nativeConnection 
          = connection.getNativeConnection();
                          
          // 集群模式和單機(jī)模式雖然執(zhí)行腳本的方法一樣,但是沒有共同的接口,所以只能分開執(zhí)行
                          
          // 集群模式
                          if (nativeConnection instanceof JedisCluster) {
                              
          return (Long)((JedisCluster)nativeConnection).eval(UNLOCK_LUA, keys, args);
                          }
                          
          //客戶端是Jedis時候(單機(jī)模式)
                          else if (nativeConnection instanceof Jedis) {
                              
          return (Long)((Jedis)nativeConnection).eval(UNLOCK_LUA, keys, args);
                          }
                          
          //這里使用 redisTemplate 中l(wèi)ettuce 客戶端
                          else{
                              DefaultRedisScript
          <Long> redisScript = new DefaultRedisScript<>();
                              redisScript.setScriptText(UNLOCK_LUA);
                              redisScript.setResultType(Long.
          class);
                              
          return (Long)redisTemplate.execute(redisScript, keys, value);
                          }
                      }
                  });
                  
          //返回最終結(jié)果
                  return RELEASE_SUCCESS.equals(result);
              }
          }
          基于zookeeper實現(xiàn)下期補(bǔ)上:


          介紹分布式鎖文章寫的比較詳細(xì):
          https://blog.csdn.net/u010963948/article/details/79006572
          主站蜘蛛池模板: 博湖县| 建宁县| 平山县| 桂东县| 宜州市| 定远县| 葵青区| 饶平县| 大荔县| 武邑县| 东丰县| 怀宁县| 兴城市| 繁峙县| 嫩江县| 新巴尔虎左旗| 龙井市| 甘南县| 行唐县| 麟游县| 蓬溪县| 横峰县| 铁岭县| 习水县| 富裕县| 内江市| 永修县| 呈贡县| 竹山县| 专栏| 邵武市| 葵青区| 北票市| 蒙山县| 循化| 如皋市| 长宁县| 台湾省| 明星| 历史| 稻城县|