<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          redis分布式鎖-java實(shí)現(xiàn)

          共 20098字,需瀏覽 41分鐘

           ·

          2021-06-11 08:42

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

            作者 |  _否極泰來(lái)

          來(lái)源 |  urlify.cn/QRbiem

          1、為什么要使用分布式鎖

          如果在一個(gè)分布式系統(tǒng)中,我們從數(shù)據(jù)庫(kù)中讀取一個(gè)數(shù)據(jù),然后修改保存,這種情況很容易遇到并發(fā)問(wèn)題。因?yàn)樽x取和更新保存不是一個(gè)原子操作,在并發(fā)時(shí)就會(huì)導(dǎo)致數(shù)據(jù)的不正確。這種場(chǎng)景其實(shí)并不少見(jiàn),比如電商秒殺活動(dòng),庫(kù)存數(shù)量的更新就會(huì)遇到。如果是單機(jī)應(yīng)用,直接使用本地鎖就可以避免。如果是分布式應(yīng)用,本地鎖派不上用場(chǎng),這時(shí)就需要引入分布式鎖來(lái)解決。

          由此可見(jiàn)分布式鎖的目的其實(shí)很簡(jiǎn)單,就是為了保證多臺(tái)服務(wù)器在執(zhí)行某一段代碼時(shí)保證只有一臺(tái)服務(wù)器執(zhí)行。

          2、為了保證分布式鎖的可用性,至少要確保鎖的實(shí)現(xiàn)要同時(shí)滿(mǎn)足以下幾點(diǎn)
          • 互斥性。在任何時(shí)刻,保證只有一個(gè)客戶(hù)端持有鎖。

          • 不能出現(xiàn)死鎖。如果在一個(gè)客戶(hù)端持有鎖的期間,這個(gè)客戶(hù)端崩潰了,也要保證后續(xù)的其他客戶(hù)端可以上鎖。

          • 保證上鎖和解鎖都是同一個(gè)客戶(hù)端。

          3、一般來(lái)說(shuō),實(shí)現(xiàn)分布式鎖的方式有以下幾種
          • 使用MySQL,基于唯一索引。

          • 使用ZooKeeper,基于臨時(shí)有序節(jié)點(diǎn)。

          • 使用Redis,基于set命令(2.6.12 版本開(kāi)始)。

          本篇文章主要講解Redis的實(shí)現(xiàn)方式。

          4、用到的redis命令

          鎖的實(shí)現(xiàn)主要基于redis的SET命令(SET詳細(xì)解釋參考這里),我們來(lái)看SET的解釋?zhuān)?/span>

          SET key value [EX seconds] [PX milliseconds] [NX|XX]

          • 將字符串值 value 關(guān)聯(lián)到 key 。

          • 如果 key 已經(jīng)持有其他值, SET 就覆寫(xiě)舊值,無(wú)視類(lèi)型。

          • 對(duì)于某個(gè)原本帶有生存時(shí)間(TTL)的鍵來(lái)說(shuō), 當(dāng) SET 命令成功在這個(gè)鍵上執(zhí)行時(shí), 這個(gè)鍵原有的 TTL 將被清除。
            可選參數(shù)

          從 Redis 2.6.12 版本開(kāi)始, SET 命令的行為可以通過(guò)一系列參數(shù)來(lái)修改:

          EX second :設(shè)置鍵的過(guò)期時(shí)間為 second 秒。SET key value EX second 效果等同于 SETEX key second value 。
          PX millisecond :設(shè)置鍵的過(guò)期時(shí)間為 millisecond 毫秒。SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
          NX :只在鍵不存在時(shí),才對(duì)鍵進(jìn)行設(shè)置操作。SET key value NX 效果等同于 SETNX key value 。
          XX :只在鍵已經(jīng)存在時(shí),才對(duì)鍵進(jìn)行設(shè)置操作。

          加鎖:使用SET key value [PX milliseconds] [NX]命令,如果key不存在,設(shè)置value,并設(shè)置過(guò)期時(shí)間(加鎖成功)。如果已經(jīng)存在lock(也就是有客戶(hù)端持有鎖了),則設(shè)置失敗(加鎖失敗)。

          解鎖:使用del命令,通過(guò)刪除鍵值釋放鎖。釋放鎖之后,其他客戶(hù)端可以通過(guò)set命令進(jìn)行加鎖。

          5、上面第二項(xiàng),說(shuō)了分布式鎖,要考慮的問(wèn)題,下面講解一下

          5.1、互斥性。在任何時(shí)刻,保證只有一個(gè)客戶(hù)端持有鎖

          redis命令是原子性的,只要客戶(hù)端調(diào)用redis的命令SET key value [PX milliseconds] [NX] 執(zhí)行成功,就算加鎖成功了

          5.2、不能出現(xiàn)死鎖。如果在一個(gè)客戶(hù)端持有鎖的期間,這個(gè)客戶(hù)端崩潰了,也要保證后續(xù)的其他客戶(hù)端可以上鎖。

          set命令px設(shè)置了過(guò)期時(shí)間,key過(guò)期失效了,就能避免死鎖了

          5.3保證上鎖和解鎖都是同一個(gè)客戶(hù)端。

          釋放鎖(刪除key)的時(shí)候,只要確保是當(dāng)前客戶(hù)端設(shè)置的value才去刪除key即可,采用lua腳本來(lái)實(shí)現(xiàn)

          在Redis中,執(zhí)行Lua語(yǔ)言是原子性,也就是說(shuō)Redis執(zhí)行Lua的時(shí)候是不會(huì)被中斷的,具備原子性,這個(gè)特性有助于Redis對(duì)并發(fā)數(shù)據(jù)一致性的支持。


          6、java代碼實(shí)現(xiàn)

          先把需要的jar包引入
                  <dependency>
                      <groupId>redis.clients</groupId>
                      <artifactId>jedis</artifactId>
                      <version>2.9.3</version>
                  </dependency>
          加鎖設(shè)置參數(shù)的實(shí)體類(lèi)
          import lombok.Data;

          //加鎖設(shè)置的參數(shù)
          @Data
          public class LockParam {
              //鎖的key
              private String lockKey;
              //嘗試獲得鎖的時(shí)間(單位:毫秒),默認(rèn)值:3000毫秒
              private Long tryLockTime;
              //嘗試獲得鎖后,持有鎖的時(shí)間(單位:毫秒),默認(rèn)值:5000毫秒
              private Long holdLockTime;

              public LockParam(String lockKey){
                  this(lockKey,1000*3L,1000*5L);
              };
              public LockParam(String lockKey,Long tryLockTime){
                  this(lockKey,tryLockTime,1000*5L);
              };
              public LockParam(String lockKey,Long tryLockTime,Long holdLockTime){
                  this.lockKey = lockKey;
                  this.tryLockTime = tryLockTime;
                  this.holdLockTime = holdLockTime;
              };
          }
          redis分布式具體代碼實(shí)現(xiàn)
          import lombok.extern.slf4j.Slf4j;
          import redis.clients.jedis.Jedis;

          import java.util.Collections;
          import java.util.UUID;

          /**
           * redis分布式鎖
           */
          @Slf4j
          public class RedisLock {

              //鎖key的前綴
              private final static String prefix_key = "redisLock:";
              //釋放鎖的lua腳本
              private final static  String unLockScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
              //執(zhí)行unLockScript腳本,釋放鎖成功值
              private final static  Long unLockSuccess = 1L;


              //加鎖設(shè)置的參數(shù)(key值、超時(shí)時(shí)間、持有鎖的時(shí)間)
              private LockParam lockParam;
              //嘗試獲得鎖的截止時(shí)間【lockParam.getTryLockTime()+System.currentTimeMillis()】
              private Long tryLockEndTime;
              //redis加鎖的key
              private String redisLockKey;
              //redis加鎖的vlaus
              private String redisLockValue;
              //redis加鎖的成功標(biāo)示
              private Boolean holdLockSuccess= Boolean.FALSE;


              //jedis實(shí)例
              private Jedis jedis;
              //獲取jedis實(shí)例
              private Jedis getJedis(){
                  return this.jedis;
              }
              //關(guān)閉jedis
              private void closeJedis(Jedis jedis){
                  jedis.close();
                  jedis = null;
              }

              public RedisLock(LockParam lockParam){
                  if(lockParam==null){
                      new RuntimeException("lockParam is null");
                  }
                  if(lockParam.getLockKey()==null || lockParam.getLockKey().trim().length()==0){
                      new RuntimeException("lockParam lockKey is error");
                  }
                  this.lockParam = lockParam;

                  this.tryLockEndTime = lockParam.getTryLockTime()+System.currentTimeMillis();
                  this.redisLockKey = prefix_key.concat(lockParam.getLockKey());
                  this.redisLockValue = UUID.randomUUID().toString().replaceAll("-","");

                  //todo 到時(shí)候可以更換獲取Jedis實(shí)例的實(shí)現(xiàn)
                  jedis = new Jedis("127.0.0.1",6379);
              }

              /**
               * 加鎖
               * @return 成功返回true,失敗返回false
               */
              public boolean lock() {
                  while(true){
                      //判斷是否超過(guò)了,嘗試獲取鎖的時(shí)間
                      if(System.currentTimeMillis()>tryLockEndTime){
                          return false;
                      }
                      //嘗試獲取鎖
                      holdLockSuccess = tryLock();
                      if(Boolean.TRUE.equals(holdLockSuccess)){
                          return true;//獲取鎖成功
                      }

                      try {
                          //獲得鎖失敗,休眠50毫秒再去嘗試獲得鎖,避免一直請(qǐng)求redis,導(dǎo)致redis cpu飆升
                          Thread.sleep(50);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }

              /**
               * 執(zhí)行一次加鎖操作:成功返回true 失敗返回false
               * @return 成功返回true,失敗返回false
               */
              private boolean tryLock() {
                  try {
                      String result = getJedis().set(redisLockKey,redisLockValue, "NX""PX", lockParam.getHoldLockTime());
                      if ("OK".equals(result)) {
                          return true;
                      }
                  }catch (Exception e){
                      log.warn("tryLock failure redisLockKey:{} redisLockValue:{} lockParam:{}",redisLockKey,redisLockValue,lockParam,e);
                  }
                  return false;
              }

              /**
               * 解鎖
               * @return 成功返回true,失敗返回false
               */
              public Boolean unlock() {
                  Object result = null;
                  try {
                      //獲得鎖成功,才執(zhí)行l(wèi)ua腳本
                      if(Boolean.TRUE.equals(holdLockSuccess)){
                          //執(zhí)行Lua腳本
                          result = getJedis().eval(unLockScript, Collections.singletonList(redisLockKey), Collections.singletonList(redisLockValue));
                          if (unLockSuccess.equals(result)) {//釋放成功
                              return true;
                          }
                      }
                  } catch (Exception e) {
                      log.warn("unlock failure redisLockKey:{} redisLockValue:{} lockParam:{} result:{}",redisLockKey,redisLockValue,lockParam,result,e);
                  } finally {
                      this.closeJedis(jedis);
                  }
                  return false;
              }
          }
          redis分布式鎖使用
          import lombok.extern.slf4j.Slf4j;

          @Slf4j
          public class test {
              static String lockKey = "666";
              public static void main(String[] args) throws InterruptedException {
                  log.info("下面測(cè)試兩個(gè)線(xiàn)程同時(shí),搶占鎖的結(jié)果");
                  Thread thread1 = new Thread(()->{
                      testRedisLock();
                  });
                  thread1.setName("我是線(xiàn)程1");
                  Thread thread2 = new Thread(()->{
                      testRedisLock();
                  });
                  thread2.setName("我是線(xiàn)程2");

                  //同時(shí)啟動(dòng)線(xiàn)程
                  thread1.start();
                  thread2.start();

                  Thread.sleep(1000*20);
                  log.info("-----------------我是一條分割線(xiàn)----------------");
                  log.info("");
                  log.info("");
                  log.info("");


                  log.info("下面是測(cè)試  一個(gè)線(xiàn)程獲取鎖成功后,由于業(yè)務(wù)執(zhí)行時(shí)間超過(guò)了設(shè)置持有鎖的時(shí)間,是否會(huì)把其他線(xiàn)程持有的鎖給釋放掉");
                  Thread thread3 = new Thread(()->{
                      testRedisLock2();
                  });
                  thread3.setName("我是線(xiàn)程3");
                  thread3.start();

                  Thread.sleep(1000*1);//暫停一秒是為了讓線(xiàn)程3獲的到鎖
                  Thread thread4 = new Thread(()->{
                      testRedisLock();
                  });
                  thread4.setName("我是線(xiàn)程4");
                  thread4.start();
              }

              public static void testRedisLock(){
                  LockParam lockParam = new LockParam(lockKey);
                  lockParam.setTryLockTime(2000L);//2秒時(shí)間嘗試獲得鎖
                  lockParam.setHoldLockTime(1000*10L);//獲得鎖成功后持有鎖10秒時(shí)間
                  RedisLock redisLock = new RedisLock(lockParam);
                  try {
                      Boolean lockFlag = redisLock.lock();
                      log.info("加鎖結(jié)果:{}",lockFlag);
                      if(lockFlag){
                          try {
                              //20秒模擬處理業(yè)務(wù)代碼時(shí)間
                              Thread.sleep(1000*5L);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }catch (Exception e) {
                      log.info("testRedisLock e---->",e);
                  }finally {
                      boolean unlockResp = redisLock.unlock();
                      log.info("釋放鎖結(jié)果:{}",unlockResp);
                  }
              }


              public static void testRedisLock2(){
                  LockParam lockParam = new LockParam(lockKey);
                  lockParam.setTryLockTime(1000*2L);//2秒時(shí)間嘗試獲得鎖
                  lockParam.setHoldLockTime(1000*2L);//獲得鎖成功后持有鎖2秒時(shí)間
                  RedisLock redisLock = new RedisLock(lockParam);
                  try {
                      Boolean lockFlag = redisLock.lock();
                      log.info("加鎖結(jié)果:{}",lockFlag);
                      if(lockFlag){
                          try {
                              //10秒模擬處理業(yè)務(wù)代碼時(shí)間
                              Thread.sleep(1000*10L);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }catch (Exception e) {
                      log.info("testRedisLock e---->",e);
                  }finally {
                      boolean unlockResp = redisLock.unlock();
                      log.info("釋放鎖結(jié)果:{}",unlockResp);
                  }
              }
          }


          這是代碼在執(zhí)行過(guò)程中,通過(guò)redis可視化工具看到的效果,可以參考一下~

          控制臺(tái)日志打印結(jié)果
          15:02:28.569 [main] INFO com.test.test - 下面測(cè)試兩個(gè)線(xiàn)程同時(shí),搶占鎖的結(jié)果
          15:02:28.645 [我是線(xiàn)程2] INFO com.test.test - 加鎖結(jié)果:true
          15:02:30.618 [我是線(xiàn)程1] INFO com.test.test - 加鎖結(jié)果:false
          15:02:30.620 [我是線(xiàn)程1] INFO com.test.test - 釋放鎖結(jié)果:false
          15:02:33.652 [我是線(xiàn)程2] INFO com.test.test - 釋放鎖結(jié)果:true
          15:02:48.614 [main] INFO com.test.test - -----------------我是一條分割線(xiàn)----------------
          15:02:48.614 [main] INFO com.test.test - 
          15:02:48.614 [main] INFO com.test.test - 
          15:02:48.614 [main] INFO com.test.test - 
          15:02:48.614 [main] INFO com.test.test - 下面是測(cè)試  一個(gè)線(xiàn)程獲取鎖成功后,由于業(yè)務(wù)執(zhí)行時(shí)間超過(guò)了設(shè)置持有鎖的時(shí)間,是否會(huì)把其他線(xiàn)程持有的鎖給釋放掉
          15:02:48.616 [我是線(xiàn)程3] INFO com.test.test - 加鎖結(jié)果:true
          15:02:50.645 [我是線(xiàn)程4] INFO com.test.test - 加鎖結(jié)果:true
          15:02:55.647 [我是線(xiàn)程4] INFO com.test.test - 釋放鎖結(jié)果:true
          15:02:58.621 [我是線(xiàn)程3] INFO com.test.test - 釋放鎖結(jié)果:false
          • 可以看到多個(gè)線(xiàn)程競(jìng)爭(zhēng)一把鎖的時(shí)候,保證了只有一個(gè)線(xiàn)程持有鎖

          • 分割線(xiàn)下面的日志也能看出,一個(gè)線(xiàn)程持有了鎖,由于處理業(yè)務(wù)代碼時(shí)間,超過(guò)了設(shè)置持有鎖的時(shí)間,通過(guò)lua腳本釋放鎖的時(shí)候,也不會(huì)把其他線(xiàn)程持有的鎖給釋放掉,保證了安全釋放了鎖


          瀏覽 65
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美午夜精品 | 成人精品在线观看视频 | 人人爱人人模 | 亚洲中文字幕无码爆乳av | 在线免费观看国产黄片 |