這篇文章主要介紹“redis怎么實(shí)現(xiàn)分布式鎖”,在日常操作中,相信很多人在Redis怎么實(shí)現(xiàn)分布式鎖問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Redis怎么實(shí)現(xiàn)分布式鎖”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
成都創(chuàng)新互聯(lián)是一家專(zhuān)注于成都網(wǎng)站建設(shè)、網(wǎng)站建設(shè)與策劃設(shè)計(jì),濱城網(wǎng)站建設(shè)哪家好?成都創(chuàng)新互聯(lián)做網(wǎng)站,專(zhuān)注于網(wǎng)站建設(shè)十年,網(wǎng)設(shè)計(jì)領(lǐng)域的專(zhuān)業(yè)建站公司;建站業(yè)務(wù)涵蓋:濱城等地區(qū)。濱城做網(wǎng)站價(jià)格咨詢:13518219792
使用Redis實(shí)現(xiàn)分布式鎖,有兩個(gè)重要函數(shù)需要介紹
SETNX命令(SET if Not eXists)
語(yǔ)法:
SETNX key value
功能:
當(dāng)且僅當(dāng) key 不存在,將 key 的值設(shè)為 value ,并返回1;若給定的 key 已經(jīng)存在,則 SETNX 不做任何動(dòng)作,并返回0。
GETSET命令
語(yǔ)法:
GETSET key value
功能:
將給定 key 的值設(shè)為 value ,并返回 key 的舊值 (old value),當(dāng) key 存在但不是字符串類(lèi)型時(shí),返回一個(gè)錯(cuò)誤,當(dāng)key不存在時(shí),返回nil。
GET命令
語(yǔ)法:
GET key
功能:
返回 key 所關(guān)聯(lián)的字符串值,如果 key 不存在那么返回特殊值 nil 。
DEL命令
語(yǔ)法:
DEL key [KEY …]
功能:
刪除給定的一個(gè)或多個(gè) key ,不存在的 key 會(huì)被忽略。
兵貴精,不在多。分布式鎖,我們就依靠這四個(gè)命令。但在具體實(shí)現(xiàn),還有很多細(xì)節(jié),需要仔細(xì)斟酌,因?yàn)樵诜植际讲l(fā)多進(jìn)程中,任何一點(diǎn)出現(xiàn)差錯(cuò),都會(huì)導(dǎo)致死鎖,hold住所有進(jìn)程。
SETNX 可以直接加鎖操作,比如說(shuō)對(duì)某個(gè)關(guān)鍵詞foo加鎖,客戶端可以嘗試
SETNX foo.lock <current unix time>
如果返回1,表示客戶端已經(jīng)獲取鎖,可以往下操作,操作完成后,通過(guò)
DEL foo.lock
命令來(lái)釋放鎖。
如果返回0,說(shuō)明foo已經(jīng)被其他客戶端上鎖,如果鎖是非堵塞的,可以選擇返回調(diào)用。如果是堵塞調(diào)用調(diào)用,就需要進(jìn)入以下個(gè)重試循環(huán),直至成功獲得鎖或者重試超時(shí)。理想是美好的,現(xiàn)實(shí)是殘酷的。僅僅使用SETNX加鎖帶有競(jìng)爭(zhēng)條件的,在某些特定的情況會(huì)造成死鎖錯(cuò)誤。
處理死鎖
在上面的處理方式中,如果獲取鎖的客戶端端執(zhí)行時(shí)間過(guò)長(zhǎng),進(jìn)程被kill掉,或者因?yàn)槠渌惓1罎ⅲ瑢?dǎo)致無(wú)法釋放鎖,就會(huì)造成死鎖。所以,需要對(duì)加鎖要做時(shí)效性檢測(cè)。因此,我們?cè)诩渔i時(shí),把當(dāng)前時(shí)間戳作為value存入此鎖中,通過(guò)當(dāng)前時(shí)間戳和Redis中的時(shí)間戳進(jìn)行對(duì)比,如果超過(guò)一定差值,認(rèn)為鎖已經(jīng)時(shí)效,防止鎖無(wú)限期的鎖下去,但是,在大并發(fā)情況,如果同時(shí)檢測(cè)鎖失效,并簡(jiǎn)單粗暴的刪除死鎖,再通過(guò)SETNX上鎖,可能會(huì)導(dǎo)致競(jìng)爭(zhēng)條件的產(chǎn)生,即多個(gè)客戶端同時(shí)獲取鎖。
C1獲取鎖,并崩潰。C2和C3調(diào)用SETNX上鎖返回0后,獲得foo.lock的時(shí)間戳,通過(guò)比對(duì)時(shí)間戳,發(fā)現(xiàn)鎖超時(shí)。
C2 向foo.lock發(fā)送DEL命令。
C2 向foo.lock發(fā)送SETNX獲取鎖。
C3 向foo.lock發(fā)送DEL命令,此時(shí)C3發(fā)送DEL時(shí),其實(shí)DEL掉的是C2的鎖。
C3 向foo.lock發(fā)送SETNX獲取鎖。
此時(shí)C2和C3都獲取了鎖,產(chǎn)生競(jìng)爭(zhēng)條件,如果在更高并發(fā)的情況,可能會(huì)有更多客戶端獲取鎖。所以,DEL鎖的操作,不能直接使用在鎖超時(shí)的情況下,幸好我們有GETSET方法,假設(shè)我們現(xiàn)在有另外一個(gè)客戶端C4,看看如何使用GETSET方式,避免這種情況產(chǎn)生。
C1獲取鎖,并崩潰。C2和C3調(diào)用SETNX上鎖返回0后,調(diào)用GET命令獲得foo.lock的時(shí)間戳T1,通過(guò)比對(duì)時(shí)間戳,發(fā)現(xiàn)鎖超時(shí)。
C4 向foo.lock發(fā)送GESET命令,
GETSET foo.lock <current unix time>
并得到foo.lock中老的時(shí)間戳T2
如果T1=T2,說(shuō)明C4獲得時(shí)間戳。
如果T1!=T2,說(shuō)明C4之前有另外一個(gè)客戶端C5通過(guò)調(diào)用GETSET方式獲取了時(shí)間戳,C4未獲得鎖。只能sleep下,進(jìn)入下次循環(huán)中。
現(xiàn)在唯一的問(wèn)題是,C4設(shè)置foo.lock的新時(shí)間戳,是否會(huì)對(duì)鎖產(chǎn)生影響。其實(shí)我們可以看到C4和C5執(zhí)行的時(shí)間差值極小,并且寫(xiě)入foo.lock中的都是有效時(shí)間錯(cuò),所以對(duì)鎖并沒(méi)有影響。
為了讓這個(gè)鎖更加強(qiáng)壯,獲取鎖的客戶端,應(yīng)該在調(diào)用關(guān)鍵業(yè)務(wù)時(shí),再次調(diào)用GET方法獲取T1,和寫(xiě)入的T0時(shí)間戳進(jìn)行對(duì)比,以免鎖因其他情況被執(zhí)行DEL意外解開(kāi)而不知。以上步驟和情況,很容易從其他參考資料中看到??蛻舳颂幚砗褪〉那闆r非常復(fù)雜,不僅僅是崩潰這么簡(jiǎn)單,還可能是客戶端因?yàn)槟承┎僮鞅蛔枞讼喈?dāng)長(zhǎng)時(shí)間,緊接著 DEL 命令被嘗試執(zhí)行(但這時(shí)鎖卻在另外的客戶端手上)。也可能因?yàn)樘幚聿划?dāng),導(dǎo)致死鎖。還有可能因?yàn)閟leep設(shè)置不合理,導(dǎo)致Redis在大并發(fā)下被壓垮。最為常見(jiàn)的問(wèn)題還有
第一種走超時(shí)邏輯
C1客戶端獲取鎖,并且處理完后,DEL掉鎖,在DEL鎖之前。C2通過(guò)SETNX向foo.lock設(shè)置時(shí)間戳T0 發(fā)現(xiàn)有客戶端獲取鎖,進(jìn)入GET操作。
C2 向foo.lock發(fā)送GET命令,獲取返回值T1(nil)。
C2 通過(guò)T0>T1+expire對(duì)比,進(jìn)入GETSET流程。
C2 調(diào)用GETSET向foo.lock發(fā)送T0時(shí)間戳,返回foo.lock的原值T2
C2 如果T2=T1相等,獲得鎖,如果T2!=T1,未獲得鎖。
第二種情況走循環(huán)走setnx邏輯
C1客戶端獲取鎖,并且處理完后,DEL掉鎖,在DEL鎖之前。C2通過(guò)SETNX向foo.lock設(shè)置時(shí)間戳T0 發(fā)現(xiàn)有客戶端獲取鎖,進(jìn)入GET操作。
C2 向foo.lock發(fā)送GET命令,獲取返回值T1(nil)。
C2 循環(huán),進(jìn)入下一次SETNX邏輯
兩種邏輯貌似都是OK,但是從邏輯處理上來(lái)說(shuō),第一種情況存在問(wèn)題。當(dāng)GET返回nil表示,鎖是被刪除的,而不是超時(shí),應(yīng)該走SETNX邏輯加鎖。走第一種情況的問(wèn)題是,正常的加鎖邏輯應(yīng)該走SETNX,而現(xiàn)在當(dāng)鎖被解除后,走的是GETST,如果判斷條件不當(dāng),就會(huì)引起死鎖,很悲催,我在做的時(shí)候就碰到了,具體怎么碰到的看下面的問(wèn)題
C1和C2客戶端調(diào)用GET接口,C1返回T1,此時(shí)C3網(wǎng)絡(luò)情況更好,快速進(jìn)入獲取鎖,并執(zhí)行DEL刪除鎖,C2返回T2(nil),C1和C2都進(jìn)入超時(shí)處理邏輯。
C1 向foo.lock發(fā)送GETSET命令,獲取返回值T11(nil)。
C1 比對(duì)C1和C11發(fā)現(xiàn)兩者不同,處理邏輯認(rèn)為未獲取鎖。
C2 向foo.lock發(fā)送GETSET命令,獲取返回值T22(C1寫(xiě)入的時(shí)間戳)。
C2 比對(duì)C2和C22發(fā)現(xiàn)兩者不同,處理邏輯認(rèn)為未獲取鎖。
此時(shí)C1和C2都認(rèn)為未獲取鎖,其實(shí)C1是已經(jīng)獲取鎖了,但是他的處理邏輯沒(méi)有考慮GETSET返回nil的情況,只是單純的用GET和GETSET值就行對(duì)比,至于為什么會(huì)出現(xiàn)這種情況?一種是多客戶端時(shí),每個(gè)客戶端連接Redis的后,發(fā)出的命令并不是連續(xù)的,導(dǎo)致從單客戶端看到的好像連續(xù)的命令,到Redis server后,這兩條命令之間可能已經(jīng)插入大量的其他客戶端發(fā)出的命令,比如DEL,SETNX等。第二種情況,多客戶端之間時(shí)間不同步,或者不是嚴(yán)格意義的同步。
我們看到foo.lock的value值為時(shí)間戳,所以要在多客戶端情況下,保證鎖有效,一定要同步各服務(wù)器的時(shí)間,如果各服務(wù)器間,時(shí)間有差異。時(shí)間不一致的客戶端,在判斷鎖超時(shí),就會(huì)出現(xiàn)偏差,從而產(chǎn)生競(jìng)爭(zhēng)條件。
鎖的超時(shí)與否,嚴(yán)格依賴時(shí)間戳,時(shí)間戳本身也是有精度限制,假如我們的時(shí)間精度為秒,從加鎖到執(zhí)行操作再到解鎖,一般操作肯定都能在一秒內(nèi)完成。這樣的話,我們上面的CASE,就很容易出現(xiàn)。所以,最好把時(shí)間精度提升到毫秒級(jí)。這樣的話,可以保證毫秒級(jí)別的鎖是安全的。
分布式鎖的問(wèn)題
1:必要的超時(shí)機(jī)制:獲取鎖的客戶端一旦崩潰,一定要有過(guò)期機(jī)制,否則其他客戶端都降無(wú)法獲取鎖,造成死鎖問(wèn)題。
2:分布式鎖,多客戶端的時(shí)間戳不能保證嚴(yán)格意義的一致性,所以在某些特定因素下,有可能存在鎖串的情況。要適度的機(jī)制,可以承受小概率的事件產(chǎn)生。
3:只對(duì)關(guān)鍵處理節(jié)點(diǎn)加鎖,良好的習(xí)慣是,把相關(guān)的資源準(zhǔn)備好,比如連接數(shù)據(jù)庫(kù)后,調(diào)用加鎖機(jī)制獲取鎖,直接進(jìn)行操作,然后釋放,盡量減少持有鎖的時(shí)間。
4:在持有鎖期間要不要CHECK鎖,如果需要嚴(yán)格依賴鎖的狀態(tài),最好在關(guān)鍵步驟中做鎖的CHECK檢查機(jī)制,但是根據(jù)我們的測(cè)試發(fā)現(xiàn),在大并發(fā)時(shí),每一次CHECK鎖操作,都要消耗掉幾個(gè)毫秒,而我們的整個(gè)持鎖處理邏輯才不到10毫秒,玩客沒(méi)有選擇做鎖的檢查。
5:sleep學(xué)問(wèn),為了減少對(duì)Redis的壓力,獲取鎖嘗試時(shí),循環(huán)之間一定要做sleep操作。但是sleep時(shí)間是多少是門(mén)學(xué)問(wèn)。需要根據(jù)自己的Redis的QPS,加上持鎖處理時(shí)間等進(jìn)行合理計(jì)算。
6:至于為什么不使用Redis的muti,expire,watch等機(jī)制,可以查一參考資料,找下原因。
鎖測(cè)試數(shù)據(jù)
第一種,鎖重試時(shí)未做sleep。單次請(qǐng)求,加鎖,執(zhí)行,解鎖時(shí)間
可以看到加鎖和解鎖時(shí)間都很快,當(dāng)我們使用
ab -n1000 -c100 'http://sandbox6.wanke.etao.com/test/test_sequence.php?tbpm=t'
AB 并發(fā)100累計(jì)1000次請(qǐng)求,對(duì)這個(gè)方法進(jìn)行壓測(cè)時(shí)。
我們會(huì)發(fā)現(xiàn),獲取鎖的時(shí)間變成,同時(shí)持有鎖后,執(zhí)行時(shí)間也變成,而delete鎖的時(shí)間,將近10ms時(shí)間,為什么會(huì)這樣?
1:持有鎖后,我們的執(zhí)行邏輯中包含了再次調(diào)用Redis操作,在大并發(fā)情況下,Redis執(zhí)行明顯變慢。
2:鎖的刪除時(shí)間變長(zhǎng),從之前的0.2ms,變成9.8ms,性能下降近50倍。
在這種情況下,我們壓測(cè)的QPS為49,最終發(fā)現(xiàn)QPS和壓測(cè)總量有關(guān),當(dāng)我們并發(fā)100總共100次請(qǐng)求時(shí),QPS得到110多。當(dāng)我們使用sleep時(shí)
單次執(zhí)行請(qǐng)求時(shí)
我們看到,和不使用sleep機(jī)制時(shí),性能相當(dāng)。當(dāng)時(shí)用相同的壓測(cè)條件進(jìn)行壓縮時(shí)
獲取鎖的時(shí)間明顯變長(zhǎng),而鎖的釋放時(shí)間明顯變短,僅是不采用sleep機(jī)制的一半。當(dāng)然執(zhí)行時(shí)間變成就是因?yàn)?,我們?cè)趫?zhí)行過(guò)程中,重新創(chuàng)建數(shù)據(jù)庫(kù)連接,導(dǎo)致時(shí)間變長(zhǎng)的。同時(shí)我們可以對(duì)比下Redis的命令執(zhí)行壓力情況
上圖中細(xì)高部分是為未采用sleep機(jī)制的時(shí)的壓測(cè)圖,矮胖部分為采用sleep機(jī)制的壓測(cè)圖,通上圖看到壓力減少50%左右,當(dāng)然,sleep這種方式還有個(gè)缺點(diǎn)QPS下降明顯,在我們的壓測(cè)條件下,僅為35,并且有部分請(qǐng)求出現(xiàn)超時(shí)情況。不過(guò)綜合各種情況后,我們還是決定采用sleep機(jī)制,主要是為了防止在大并發(fā)情況下把Redis壓垮,很不行,我們之前碰到過(guò),所以肯定會(huì)采用sleep機(jī)制。
到此,關(guān)于“Redis怎么實(shí)現(xiàn)分布式鎖”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
分享文章:Redis怎么實(shí)現(xiàn)分布式鎖
文章鏈接:http://jinyejixie.com/article6/gggdig.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供電子商務(wù)、網(wǎng)頁(yè)設(shè)計(jì)公司、、靜態(tài)網(wǎng)站、網(wǎng)站策劃、虛擬主機(jī)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)