Redisson 分佈式鎖的正確使用

背景介紹

前段時間,在寫公司的一個項目的時候,用到了分佈式鎖,一個同事告訴我說,分佈式鎖解鎖在高併發的時候會報錯。

下面看下模擬代碼:

這裏鎖的時間是 5 秒,而業務執行的時間是 20 秒。這裏模擬的是鎖的時間少於業務執行的時間。

第二次執行的時候,就會報錯,如下:

java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 660a38bb-c50b-4117-8ee2-67da7b4303c6 thread-id: 62

錯誤分析

通過這個錯誤信息,也知道該如何解決這個問題,我們只需要判斷是當前線程再去解鎖,就不會報錯的。

可是爲什麼會報錯呢?

所以,我們需要先去搞清楚具體的執行流程。

最開始,我以爲,如果被鎖住,運行到 ① 就會被返回,後面經過測試,實際上是會走到第 ② 步,嘗試獲取不到鎖,就會返回,在返回之前呢,會執行 finally 的代碼,因爲 redisson 對鎖有續租的功能,所以,這時候鎖還是鎖住的,解鎖就會報錯,也就是第 ③ 步。

實際上,我們的想法的,如果業務執行出錯,我們在 finally 進行解鎖,以防止程序死鎖。

顯然這樣寫代碼,不是我們所期望的,並且代碼也有問題。

優化代碼

@RequestMapping("/try-lock")
public String tryLock() {
    
    RLock rLock = redissonClient.getLock("demo-spring-boot-redisson:try-lock");
    if (Objects.isNull(rLock)) {
        return "lock exception";
    }
    
    boolean tryLock;
    try {
        tryLock = rLock.tryLock(3, 60, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        return "get lock exception";
    }
    if (!tryLock) {
        return "get lock failed";
    }
    
    try {
        TimeUnit.SECONDS.sleep(20);
        return "success";
    } catch (Exception e) {
        return "business exception";
    } finally {
        if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
            rLock.unlock();
        }
    }
}

以上,便是優化後的代碼,我們來一起分析一下。

分佈式加鎖主要分爲三步。

第一步,主要是獲取 RLock 對象,並且我們對它做了判空。

RLock rLock = redissonClient.getLock("demo-spring-boot-redisson:try-lock");
if (Objects.isNull(rLock)) {
    return "lock exception";
}

第二步,嘗試加鎖,加鎖失敗,返回加鎖失敗。

boolean tryLock;
try {
    tryLock = rLock.tryLock(3, 60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
    return "get lock exception";
}
if (!tryLock) {
    return "get lock failed";
}

這裏我們用的是 tryLock,第一個參數 waitTime,意思是等待 5 秒,如果還沒獲取到,就不再等待。第二個參數是 leaseTime,意思是鎖的釋放時間。

第三步,就是我們業務代碼。

try {
    TimeUnit.SECONDS.sleep(20);
    return "success";
} catch (Exception e) {
    return "business exception";
} finally {
    if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
        rLock.unlock();
    }
}

在 finally 裏,我們做鎖的釋放操作,在釋放之前,我們對鎖的狀態和是否是當前線程做了判斷。

OK,如果你在實際的業務中如果遇到什麼問題,歡迎留言探討。

引申分析

    1. lock 和 tryLock 區別?

簡單來說,lock 會一直阻塞,而 tryLock 加鎖失敗,會返回 false。

    1. 如果鎖的時間少於業務的時間,會怎麼樣?

通過上面的分析,我們知道 tryLock 會加鎖失敗,而 lock,在鎖到釋放時間後,即便業務沒有執行完,也會繼續執行,並且不會報錯。

@RequestMapping("/lock")
public String lock() {
    RLock rLock = redissonClient.getLock("demo-spring-boot-redisson:lock");
    if (Objects.isNull(rLock)) {
        return "exception";
    }
    try {
        rLock.lock(5, TimeUnit.SECONDS);
        System.out.println("execute business");
        TimeUnit.SECONDS.sleep(20);
        return "success";
    } catch (Exception e) {
        return "lock exception";
    } finally {
        if (rLock.isLocked()) {
            rLock.unlock();
        }
    }
}
    1. 在解鎖的時候,不判斷鎖的狀態,會報錯嗎,反正都會解鎖?

tryLock 不會。

而 lock 會報錯,報錯信息如下:

java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 8b7b0374-506b-442f-9bc4-9e1c1cbf4d46 thread-id: 61

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章