SpringBoot實現Redis分布式鎖


前言

當不同的進程,必須以獨占資源的方式實現資源共享,就需要用到分布式鎖。

安全和穩定性

分布式鎖的實現,必須滿足以下2個特性

  • 獨享互斥:在任意一個時刻,只能有一個客戶端持有鎖
  • 無死鎖:既然有加鎖,則必須存在解鎖。即使持有鎖的客戶端崩潰宕機,鎖仍然允許被其他客戶端獲取,不能造成無限期的等待

例子1

@Autowired
private StringRedisTemplate stringRedisTemplate;

@GetMapping("/lock")
public void lock1() throws InterruptedException {
    String lockKey = "lockKey", lockValue = "lockValue";
    Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, Duration.ofSeconds(10));
    if (success){
        try{
            System.out.println(String.format("Thread-%d:Success", Thread.currentThread().getId()));
        }
        finally {
            stringRedisTemplate.delete(lockKey);
        }
    }
}
  • 獨享互斥:setIfAbsent等同於Redis的SETNX命令,當key不存在才生效,返回的是true;當key存在時返回false。
  • 無死鎖:setIfAbsent第3個參數是key超時時間,就算中途應用服務掛了,等時間到了key自動失效。另外,finally處理刪除key操作,及時釋放鎖。

用JMeter發送1000個並發請求,結果如下:

Thread-227:Success
Thread-88:Success
Thread-116:Success
Thread-202:Success
Thread-185:Success
Thread-97:Success

但是,上述Demo存在以下問題:

  1. 客戶端A獲取到鎖資源,同時設置超時時間10s,緊接着A被其他操作堵塞了進程
  2. 10s過后,由於A的業務尚未執行完,鎖過期自動失效了
  3. 客戶端B成功獲取鎖資源
  4. 此時A的業務邏輯執行完畢,做了釋放鎖操作,此時刪除的KEY是客戶端B加鎖的KEY
  5. 客戶端C嘗試獲取鎖資源,由於KEY已經被A刪掉了,所以C也加鎖成功,和客戶端B存在了並發的問題

例子2

public void lock3() throws InterruptedException {
    String lockKey = "lockKey";
    //lock value改成唯一性的UUID
    String lockValue = UUID.randomUUID().toString();
    Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, Duration.ofSeconds(10));
    if (success){
        try{
            System.out.println(String.format("Thread-%d:Success", Thread.currentThread().getId()));
        }
        finally {
        	//釋放鎖不再是簡單的DELETE KEY
            releaseLock(lockKey, lockValue);
        }
    }
}
private boolean releaseLock(String key, String value){
    String srcValue = stringRedisTemplate.opsForValue().get(key);
    if (StrUtil.isEmpty(srcValue)){
        return true;
    }
    else if (srcValue.equals(value)){
        stringRedisTemplate.delete(key);
        return true;
    }
    return false;
}

上面增加了releaseLock函數:

  1. 先從Redis取出KEY
  2. 如果KEY不存在,說明已經過期了,這里直接return true,當做釋放鎖資源成功
  3. 如果KEY存在,把VALUE和加鎖時用的UUID做比較,相等就說明這是自己占用的鎖資源,直接DELETE;如果不相等,就不要做DELETE操作,說明這是其他客戶端加的鎖

擴展鎖

如果你的業務邏輯可以拆解成多個小步驟,可以將鎖的有效時間設置短一些。在業務處理的過程中,當發現KEY的有效期很短時,再次延長其有效期(前提還是key存在並且value是之前設置的value)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM