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