1. 程式人生 > 程式設計 >SpringBoot + Redisson實現分散式鎖

SpringBoot + Redisson實現分散式鎖

一、什麼是分散式?

要想說什麼是分散式,那麼首先要知道分散式之前的系統是什麼樣的架構,之前的架構又存在什麼樣的問題?

  • 單體架構

    分散式之前就是單體架構,單體架構顧名思義就是將所有的業務功能打包在一個應用中,然後部署在伺服器上。如果我們把單體架構比作一個汽車工廠,那麼從汽車發動機到汽車上的一個螺絲釘都需要由它來負責完成,如果有一天這家工廠由於自然災害的原因導致業務量急劇下滑,甚至停止生產,那麼這個整個工廠無論是造發動機的,還是造螺絲釘的全部都得停工。落實到網際網路就是單體架構開發成本高、出現故障影響的範圍大,很難適應現在的網際網路專案。

  • 分散式

    既然單體架構不足以解決現在存在的高併發、高效能、高可用的要求,那麼分散式就來了,原來我們把一個汽車所有的零件都放在一家工廠進行生產,結果會出現上面停工的情況,那麼分散式怎麼解決的?所謂分散式就是把所有的零件分開生產,每個工廠根據自己的特長來生產相應的汽車配件,最後統一組裝,即使因為一些其他原因,導致某個工廠停止了生產,但是依舊不影響其他工廠的生產進度,我們可以通過小的代價來尋找其他工廠來代替停工的工廠,確保我們的生產任務可以正常的運轉。

二、為什麼要有分散式鎖?

  • 如果沒有分散式鎖

    解釋這個問題之前,我們先來說下,如果沒有分散式鎖會存在什麼問題,舉個經典的例子,如果我們有一個購物網站,有一件商品只有10件可賣,此時使用者A進來後還有1個庫存,然後使用者A下單付款,扣減庫存,在使用者A付款的這個過程中,恰好使用者B進來了,也看到還有一個庫存,使用者B也開始下單付款,扣減庫存,那麼這個過程很明顯已經出了問題,出現了超賣的問題。

  • 分散式鎖

    如果有了分散式鎖就不會出現超賣問題,舉個簡單的例子,分散式就像是銀行的ATM,你去ATM取錢,如果你去了恰巧裡面有人,那麼你肯定是進不去的,必須等裡面的人出來之後你才能進去,同樣的,如果你正在取錢,如果外面還有人等著取錢,那麼無論如何他都必須等你取完出來,他才能進去,要麼他不取了回家去。

三、Redis實現的分散式鎖

簡單的介紹了分散式和分散式鎖,之後我們現在來看看如何實現一個分散式鎖,先看我之前使用Redis寫個一個分散式鎖,看看有什麼問題?

public Boolean lock(String key,Long waitTime,Long expireTime) {

        String value = UUID.randomUUID().toString().replaceAll("-","").toLowerCase();
        Boolean flag = setNx(key,value,expireTime,TimeUnit.SECONDS);

        // 嘗試獲取鎖 成功返回
        if
(flag) { return flag; } else { // 獲取失敗 // 現在時間 long newTime = System.currentTimeMillis(); // 等待過期時間 long loseTime = newTime + waitTime; // 不斷嘗試獲取鎖成功返回 11 < 11 while (System.currentTimeMillis() < loseTime) { Boolean testFlag = setNx(key,TimeUnit.MILLISECONDS); if (testFlag) { return testFlag; } try { Thread.sleep(1000); } catch (InterruptedException e) { } } } return false; } 複製程式碼

首先嚐試獲取鎖,如果獲取到了同時設定過期時間(原子操作,防止死鎖),獲取到了直接返回,簡單看沒什麼問題,但是如果說,我設定了10秒的過期時間,但是我的業務執行了12秒,那麼此時其他執行緒也進來了,那麼這個鎖根本就沒有起到任何作用,注意:大家千萬不要說那我把過期時間設定為1分鐘設定10分鐘不就好了,千萬不要這麼幹,治標不治本。

其實辦法肯定是有的,在獲取到鎖之後,然後開一個守護執行緒,判斷當前執行緒時候執行結束,如果沒有那就重置鎖的過期時間。但是我們這裡就不說了,而是直接用別人封裝好的框架。

四、Redisson分散式鎖

1、首先引入maven

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.11.5</version>
</dependency>
複製程式碼

2、構建Redisson例項(基於SpringBoot)

@Bean
public RedissonClient redissonClient() {

    Config config = new Config();

    config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456");

    RedissonClient redissonClient = Redisson.create(config);
    return redissonClient;
}
複製程式碼

3、使用
在這裡插入圖片描述
其實單從使用來說是非常簡單的,這裡需要跟大家說一點,就是lock()這個方法,我們先看原始碼,這個方法是可以設定過期時間的,但是它並不會去進行檢查任務是否執行結束,如果任務沒有執行結束,然後鎖的過期時間到了,執行緒中斷,就會出現異常。

public void lock(long leaseTime,TimeUnit unit) {
        try {
            this.lock(leaseTime,unit,false);
        } catch (InterruptedException var5) {
            throw new IllegalStateException();
        }
    }
複製程式碼

異常資訊
在這裡插入圖片描述
4、較為完善的方法,lock()

這個方法不需要傳遞任何引數,它的底層方法會通過我們剛才說的原理,去校驗當前任務是否執行結束,如果沒有執行結束,那麼相應的就會延長鎖的過期時間。

底層實現

private void lock(long leaseTime,TimeUnit unit,boolean interruptibly) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
        Long ttl = this.tryAcquire(leaseTime,threadId);
        if (ttl != null) {
            RFuture<RedissonLockEntry> future = this.subscribe(threadId);
            this.commandExecutor.syncSubscription(future);

            try {
            
                // 校驗任務是否執行結束
                while(true) {
                    ttl = this.tryAcquire(leaseTime,threadId);
                    if (ttl == null) {
                        return;
                    }

                    if (ttl >= 0L) {
                        try {
                            this.getEntry(threadId).getLatch().tryAcquire(ttl,TimeUnit.MILLISECONDS);
                        } catch (InterruptedException var13) {
                            if (interruptibly) {
                                throw var13;
                            }

                            this.getEntry(threadId).getLatch().tryAcquire(ttl,TimeUnit.MILLISECONDS);
                        }
                    } else if (interruptibly) {
                        this.getEntry(threadId).getLatch().acquire();
                    } else {
                        this.getEntry(threadId).getLatch().acquireUninterruptibly();
                    }
                }
            } finally {
                this.unsubscribe(future,threadId);
            }
        }
    }
複製程式碼

其實Redisson還有其他好多的方法來解決現在網際網路中的好多問題,但是我這裡就不一一介紹了,大家如果想了解更多的東西,可以去Redisson官網。

Redisson官網