springboot2 redis 及 redission 分佈式鎖(分佈式事務)

參考:https://www.fengyunxiao.cn/

springboot 在單機模式下很好解決搶票,秒殺等需要一個一個執行的業務,可以使用jvm自帶的synchronized進行上鎖,避免搶了同一個。分佈式場景下(比如在多個服務器同時部署了多個項目)使用synchronized關鍵字不能保證業務不出問題,需要使用中心化的解決方法,如redis。

方法1:單純使用 redis 作爲分佈式鎖,比較簡單,但是存在幾個很難完美解決的問題。

// 獲取原子鎖(setnx),同時設置超時時間,該方法是原子性的。
public boolean getLock(String key, String value) {
    Boolean result = stringRedisTemplate.boundValueOps(key).setIfAbsent(value, 30, TimeUnit.SECONDS);
    return result == null ? false : result;
}

// 刪除原子鎖,根據key和value同時刪除,非原子性。最好使用lua,保證原子性,因爲我沒有使用過lua,所以先用非原子性的。後期更新。
public void deleteLock(String key, String value) {
    String s = stringRedisTemplate.boundValueOps(key).get();
    if (value!= null && value.equals(s)) {
        stringRedisTemplate.delete(key);
    }
}

單純使用 redis 作爲分佈式鎖需要注意以下事項,現在存在 3 和 5 兩個問題很難解決:

  1. 設置鎖的超時過期時間:防止中途出現錯誤導致代碼中斷,沒有執行釋放鎖,其他線程再也不會搶到鎖。
  2. try…finally { 釋放鎖 }:防止中途出現異常,沒有執行到釋放鎖。
  3. 若代碼執行時間比鎖的超時時間長,會造成嚴重問題:A線程未執行完,但是超時鎖解了,B線程進入了同時上鎖,此時A執行完,釋放了B線程的鎖,導致C線程又進入。(臨時解決方案:鎖的時間儘可能長,同時用4的方案)
  4. 比較key的value相同再解鎖,如果線程1超時解鎖了,線程2獲得新的鎖進入了,此時1線程執行完因爲線程1的鎖的value和線程2鎖的value不相同,所以不能解開線程2的鎖,所以線程3無法進入。
  5. A和B同時訪問,B防止搶不到鎖就結束(體驗不好),最好B線程能持續嘗試幾秒,超時再結束。如果使用 while 循環持續搶鎖,可能性能消耗很大。如果使用 while 循環搶鎖 n 次後結束,設計代碼又很複雜冗長。
  6. 主從redids中,主節點掛掉後進行主從切換後,從節點沒法獲取已經上鎖的進程的鎖id進行線程的重新上鎖。(只能使用單機redis解決)

方法2:使用 redisson 作爲分佈式鎖

  1. 首先導入依賴
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.12.0</version>
</dependency>
  1. 代碼
// 導入依賴
@Autowired private RedissonClient redissonClient;

// 獲取鎖
RLock lock = redissonClient.getLock("ddddd");
// 上鎖
lock.lock();
// 解鎖
lock.unlock();

(解決 redis 的3的問題):redisson有看門狗機制,自動續期當前鎖的超時時間(默認20秒檢查一次,鎖的超時時間默認30秒),保證鎖的時間>代碼執行時間。如果手動設置了超時時間<30秒,同時也要修改看門狗的檢查時間間隔。redissonClient.getConfig().setLockWatchdogTimeout();
(解決 redis 的5的問題):使用 lock() 方法,如果沒有搶到鎖會持續進行搶鎖,直到搶到再執行操作代碼。避免了n個線程同時訪問,n-1個線程沒有搶到鎖,就直接返回給用戶無效的信息,體驗很差。也可以使用 lock.tryLock(5, TimeUnit.SECONDS),只在 5 秒內嘗試搶鎖,如果 5 秒內搶不到鎖,返回 false,繼續執行代碼。


// 獲取鎖
RLock lock = redissonClient.getLock("ddddd");
// 嘗試上鎖,5秒內出結果
boolean islock = lock.tryLock(5, TimeUnit.SECONDS); 
if ( islock ) {
	// 搶到鎖的代碼
	// 獲取到鎖,需要解鎖
    lock.unlock();
} else {
    // 沒有搶到鎖的代碼,不需要解鎖
}
發佈了48 篇原創文章 · 獲贊 55 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章