更新時間:2023-02-15 16:00:07 來源:動力節點 瀏覽3054次
1.為什么需要分布式鎖?
public synchronized void test() {
System.out.println("獲取到鎖");
}
public void test2() {
synchronized (Test.class) {
System.out.println("獲取到鎖");
}
}
假設我們把上述代碼部署到多臺服務器上,這個互斥鎖還能生效嗎?答案是否定的,這時分布式鎖應運而生。
2.Redis分布式鎖?
接下來我給大家講解完整的演變過程,讓大家更深刻的理解分布式鎖。
Redis setnx
線程1申請加鎖,這時沒有人持有鎖,加鎖成功:
127.0.0.1:6379> setnx lock 1
(integer) 1
線程2申請加鎖,此時發現有人持有鎖未釋放,加鎖失?。?/p>
127.0.0.1:6379> setnx lock 1
(integer) 0
線程1執行完成業務邏輯后,執行DEL命令釋放鎖:
127.0.0.1:6379> del lock
(integer) 1
存在問題:
①假設線程1執行到一半,系統掛了,這時鎖還沒釋放,就會造成死鎖。
②如果Redis加鎖后,Master還沒同步給Slave就掛了,會導致有兩個客戶端獲取到鎖
解決方案:setnx expire
Redis setnx expire
為了解決上述死鎖問題,我們在setnx后,給這個key加上失效時間。
此時線程1加鎖的代碼改成:
127.0.0.1:6379> setnx lock 1 ## 加鎖
(integer) 1
127.0.0.1:6379> expire lock 3 ## 設置 key 3秒失效
(integer) 1
存在問題:
①假設setnx lock 1執行成功了,但是expire lock 3執行失敗了,還是會存在死鎖問題,這兩個命令需要保證原子性。
②失效時間是我們寫死的,不能自動續約,如果業務執行時間超過失效時間,會出現線程1還在執行,線程2就加鎖成功了,并有沒達到互斥效果。
③如果Redis加鎖后,Master還沒同步給Slave就掛了,會導致有兩個客戶端獲取到鎖
解決方案:RedissonLock
RedissonLock
上述兩個問題,RedissonLock都解決了,我通過源碼給大家剖析,看RedissonLock是如何解決的,基礎好的小伙伴可以好好讀讀源碼,其實RedissonLock源碼也不難。
我先寫結論,基礎較弱的小伙伴,只要記得結論就行:
①RedisssonLock底層使用的是lua腳本執行的redis指令,lua腳本可以保證加鎖和失效指令的原子性。
②RedisssonLock底層有個看門狗機制,加鎖成功后,會開啟一個定時調度任務,每隔10秒去檢查鎖是否釋放,如果沒有釋放,把失效時間刷新成30秒。這樣鎖就可以一直續期,不會釋放。
我看的是3.12.5版本源碼,不同版本實現上可能存在一些差異。
應用程序加鎖代碼:
RLock lock = redissonLock.getLock("anyLock");
lock.lock();
RedissonLock加鎖核心代碼:
RedissonLock獲取鎖核心代碼:
底層加鎖邏輯:
KEYS[1] = anyLock,鎖的名稱。
ARGV[1] = 30000,失效時間,通過lockWatchdogTimeout配置。
ARGV[2] = c1b51ddb-1505-436c-a308-b3b75b4bd407:1,他是ConnectionManager的ID,我們可以簡單的把它理解為一個客戶端的一個線程對應的唯一標志性。
RedissonLock解鎖核心代碼:
存在問題:如果redis是單節點,存在單節點故障問題;如果做主從架構,Redis加鎖后,Master還沒同步給Slave就掛了,會導致有兩個客戶端獲取到鎖
有小伙伴問我,如果這里我用集群會存在這個問題嗎?集群的本質是分片,這個key最終還是會落到某個具體的節點,這個節點要么是單獨存在,要么是主從架構,所以還是會存在上述問題。
解決方案:RedLock
補充:雖然RedLock可以解決上述問題,但是在生產環境中我們很少使用,因為它部署成本很高,相比RedissonLock性能也略微有所下降?。
如果業務能接受極端情況下存在互斥失敗問題,并且對性能要求比較高,我們會選擇RedissonLock,并做好響應?的兜底方案。
如果業務對數據要求絕對正確,?我們會采用Zookeeper來做分布式鎖。?
Redlock
我們假設有5個完全相互獨立的Redis Master單機節點,所以我們需要在5臺機器上面運行這些實例,如下圖所示(請注意這張圖中5個Master節點完全相互獨立)
為了取到鎖,客戶端應該執行以下操作:
①獲取當前Unix時間,以毫秒為單位。
②依次嘗試從N個Master實例使用相同的key和隨機值獲取鎖(假設這個key是LOCK_KEY)。當向Redis設置鎖時,客戶端應該設置一個網絡連接和響應超時時間,這個超時時間應該小于鎖的失效時間。例如你的鎖自動失效時間為10秒,則超時時間應該在5-50毫秒之間。這樣可以避免服務器端Redis已經掛掉的情況下,客戶端還在死死地等待響應結果。如果服務器端沒有在規定時間內響應,客戶端應該盡快嘗試另外一個Redis實例。
③客戶端使用當前時間減去開始獲取鎖時間(步驟1記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數的Redis節點都取到鎖,并且使用的時間小于鎖失效時間時,鎖才算獲取成功。
④如果取到了鎖,key的真正有效時間等于有效時間減去獲取鎖所使用的時間(步驟3計算的結果)。
⑤如果因為某些原因,獲取鎖失敗(沒有在至少N/2+1個Redis實例取到鎖或者取鎖時間已經超過了有效時間),客戶端應該在所有的Redis實例上進行解鎖(即便某些Redis實例根本就沒有加鎖成功)。
缺點:像我們系統,并發量比較大,生產環境必須要做分片才能扛住并發,像上述方案,我們需要準備5個Redis集群,這種機器成本是非常高的。
以上就是“還是一樣高頻出現的Java分布式鎖面試題”,你能回答上來嗎?如果想要了解更多的Java面試題相關內容,可以關注動力節點Java官網。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習