一耙旦、寫在前面
現(xiàn)在面試,一般都會(huì)聊聊分布式系統(tǒng)這塊的東西萝究。通常面試官都會(huì)從服務(wù)框架(Spring Cloud免都、Dubbo)聊起,一路聊到分布式事務(wù)帆竹、分布式鎖绕娘、ZooKeeper等知識(shí)。所以咱們這篇文章就來聊聊分布式鎖這塊知識(shí)栽连,具體的來看看Redis分布式鎖的實(shí)現(xiàn)原理险领。
說實(shí)話,如果在公司里落地生產(chǎn)環(huán)境用分布式鎖的時(shí)候,一定是會(huì)用開源類庫的绢陌,比如Redis分布式鎖挨下,一般就是用Redisson框架就好了,非常的簡便易用下面。
大家如果有興趣,可以去看看Redisson的官網(wǎng)绩聘,看看如何在項(xiàng)目中引入Redisson的依賴沥割,然后基于Redis實(shí)現(xiàn)分布式鎖的加鎖與釋放鎖。下面給大家看一段簡單的使用代碼片段凿菩,先直觀的感受一下:
怎么樣机杜,上面那段代碼,是不是感覺簡單的不行衅谷!此外椒拗,人家還支持redis單實(shí)例、redis哨兵获黔、redis cluster蚀苛、redis master-slave等各種部署架構(gòu),都可以給你完美實(shí)現(xiàn)玷氏。
二堵未、Redisson實(shí)現(xiàn)Redis分布式鎖的底層原理
好的,接下來就通過一張手繪圖盏触,給大家說說Redisson這個(gè)開源框架對Redis分布式鎖的實(shí)現(xiàn)原理渗蟹。
(1)加鎖機(jī)制
咱們來看上面那張圖,現(xiàn)在某個(gè)客戶端要加鎖赞辩。如果該客戶端面對的是一個(gè)redis cluster集群雌芽,他首先會(huì)根據(jù)hash節(jié)點(diǎn)選擇一臺(tái)機(jī)器。這里注意辨嗽,僅僅只是選擇一臺(tái)機(jī)器世落!這點(diǎn)很關(guān)鍵!緊接著糟需,就會(huì)發(fā)送一段lua腳本到redis上岛心,那段lua腳本如下所示:
為啥要用lua腳本呢?因?yàn)橐淮筵鐝?fù)雜的業(yè)務(wù)邏輯篮灼,可以通過封裝在lua腳本中發(fā)送給redis忘古,保證這段復(fù)雜業(yè)務(wù)邏輯執(zhí)行的原子性。
那么诅诱,這段lua腳本是什么意思呢髓堪?這里KEYS[1]代表的是你加鎖的那個(gè)key,比如說:RLock lock = redisson.getLock("myLock");這里你自己設(shè)置了加鎖的那個(gè)鎖key就是“myLock”。
ARGV[1]代表的就是鎖key的默認(rèn)生存時(shí)間干旁,默認(rèn)30秒驶沼。ARGV[2]代表的是加鎖的客戶端的ID,類似于下面這樣:8743c9c0-0795-4907-87fd-6c719a6b4586:1
給大家解釋一下争群,第一段if判斷語句回怜,就是用“exists myLock”命令判斷一下,如果你要加鎖的那個(gè)鎖key不存在的話换薄,你就進(jìn)行加鎖玉雾。如何加鎖呢?很簡單轻要,用下面的命令:hset myLock
8743c9c0-0795-4907-87fd-6c719a6b4586:1 1复旬,通過這個(gè)命令設(shè)置一個(gè)hash數(shù)據(jù)結(jié)構(gòu),這行命令執(zhí)行后冲泥,會(huì)出現(xiàn)一個(gè)類似下面的數(shù)據(jù)結(jié)構(gòu):
上述就代表“8743c9c0-0795-4907-87fd-6c719a6b4586:1”這個(gè)客戶端對“myLock”這個(gè)鎖key完成了加鎖驹碍。接著會(huì)執(zhí)行“pexpire myLock 30000”命令,設(shè)置myLock這個(gè)鎖key的生存時(shí)間是30秒凡恍。好了志秃,到此為止,ok嚼酝,加鎖完成了洽损。
(2)鎖互斥機(jī)制
那么在這個(gè)時(shí)候,如果客戶端2來嘗試加鎖革半,執(zhí)行了同樣的一段lua腳本碑定,會(huì)咋樣呢?很簡單又官,第一個(gè)if判斷會(huì)執(zhí)行“exists myLock”延刘,發(fā)現(xiàn)myLock這個(gè)鎖key已經(jīng)存在了。接著第二個(gè)if判斷六敬,判斷一下碘赖,myLock鎖key的hash數(shù)據(jù)結(jié)構(gòu)中,是否包含客戶端2的ID外构,但是明顯不是的普泡,因?yàn)槟抢锇氖强蛻舳?的ID。
所以审编,客戶端2會(huì)獲取到pttl myLock返回的一個(gè)數(shù)字撼班,這個(gè)數(shù)字代表了myLock這個(gè)鎖key的剩余生存時(shí)間。比如還剩15000毫秒的生存時(shí)間垒酬。此時(shí)客戶端2會(huì)進(jìn)入一個(gè)while循環(huán)砰嘁,不停的嘗試加鎖件炉。
(3)watch dog自動(dòng)延期機(jī)制
客戶端1加鎖的鎖key默認(rèn)生存時(shí)間才30秒,如果超過了30秒矮湘,客戶端1還想一直持有這把鎖斟冕,怎么辦呢?
簡單缅阳!只要客戶端1一旦加鎖成功磕蛇,就會(huì)啟動(dòng)一個(gè)watch dog看門狗,他是一個(gè)后臺(tái)線程十办,會(huì)每隔10秒檢查一下秀撇,如果客戶端1還持有鎖key,那么就會(huì)不斷的延長鎖key的生存時(shí)間橘洞。
(4)可重入加鎖機(jī)制
那如果客戶端1都已經(jīng)持有了這把鎖了捌袜,結(jié)果可重入的加鎖會(huì)怎么樣呢说搅?比如下面這種代碼:
這時(shí)我們來分析一下上面那段lua腳本炸枣。第一個(gè)if判斷肯定不成立,“exists myLock”會(huì)顯示鎖key已經(jīng)存在了弄唧。第二個(gè)if判斷會(huì)成立适肠,因?yàn)閙yLock的hash數(shù)據(jù)結(jié)構(gòu)中包含的那個(gè)ID,就是客戶端1的那個(gè)ID候引,也就是“8743c9c0-0795-4907-87fd-6c719a6b4586:1”
此時(shí)就會(huì)執(zhí)行可重入加鎖的邏輯侯养,他會(huì)用:
incrby myLock 8743c9c0-0795-4907-87fd-6c71a6b4586:1 1 ,通過這個(gè)命令澄干,對客戶端1的加鎖次數(shù)逛揩,累加1。此時(shí)myLock數(shù)據(jù)結(jié)構(gòu)變?yōu)橄旅孢@樣:
大家看到了吧麸俘,那個(gè)myLock的hash數(shù)據(jù)結(jié)構(gòu)中的那個(gè)客戶端ID辩稽,就對應(yīng)著加鎖的次數(shù)
(5)釋放鎖機(jī)制
如果執(zhí)行l(wèi)ock.unlock(),就可以釋放分布式鎖从媚,此時(shí)的業(yè)務(wù)邏輯也是非常簡單的逞泄。其實(shí)說白了,就是每次都對myLock數(shù)據(jù)結(jié)構(gòu)中的那個(gè)加鎖次數(shù)減1拜效。如果發(fā)現(xiàn)加鎖次數(shù)是0了喷众,說明這個(gè)客戶端已經(jīng)不再持有鎖了,此時(shí)就會(huì)用:“del myLock”命令紧憾,從redis里刪除這個(gè)key到千。然后呢,另外的客戶端2就可以嘗試完成加鎖了赴穗。這就是所謂的分布式鎖的開源Redisson框架的實(shí)現(xiàn)機(jī)制父阻。
一般我們在生產(chǎn)系統(tǒng)中愈涩,可以用Redisson框架提供的這個(gè)類庫來基于redis進(jìn)行分布式鎖的加鎖與釋放鎖。
(6)上述Redis分布式鎖的缺點(diǎn)
其實(shí)上面那種方案最大的問題加矛,就是如果你對某個(gè)redis master實(shí)例履婉,寫入了myLock這種鎖key的value,此時(shí)會(huì)異步復(fù)制給對應(yīng)的master slave實(shí)例斟览。但是這個(gè)過程中一旦發(fā)生redis master宕機(jī)毁腿,主備切換,redis slave變?yōu)榱藃edis master苛茂。
接著就會(huì)導(dǎo)致已烤,客戶端2來嘗試加鎖的時(shí)候,在新的redis master上完成了加鎖妓羊,而客戶端1也以為自己成功加了鎖胯究。此時(shí)就會(huì)導(dǎo)致多個(gè)客戶端對一個(gè)分布式鎖完成了加鎖。這時(shí)系統(tǒng)在業(yè)務(wù)語義上一定會(huì)出現(xiàn)問題躁绸,導(dǎo)致各種臟數(shù)據(jù)的產(chǎn)生裕循。
所以這個(gè)就是redis cluster,或者是redis master-slave架構(gòu)的主從異步復(fù)制導(dǎo)致的redis分布式鎖的最大缺陷:在redis master實(shí)例宕機(jī)的時(shí)候净刮,可能導(dǎo)致多個(gè)客戶端同時(shí)完成加鎖剥哑。
作者:石杉的架構(gòu)筆記
鏈接:https://juejin.im/post/5bf3f15851882526a643e207
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)淹父,非商業(yè)轉(zhuǎn)載請注明出處株婴。