程序员修神之路--redis做分布式锁可能不那么简单
菜菜哥,復(fù)聯(lián)四上映了,要不要一起去看看?
又想騙我電影票,對(duì)不對(duì)?
呵呵,想去看了叫我呀
看來你工作不飽和呀
哪有,這兩天我剛基于redis寫了一個(gè)分布式鎖,很簡單
不管你基于什么做分布式鎖,你覺得很簡單嗎?來來來
在計(jì)算機(jī)世界里,對(duì)于鎖大家并不陌生,在現(xiàn)代所有的語言中幾乎都提供了語言級(jí)別鎖的實(shí)現(xiàn),為什么我們的程序有時(shí)候會(huì)這么依賴鎖呢?這個(gè)問題還是要從計(jì)算機(jī)的發(fā)展說起,隨著計(jì)算機(jī)硬件的不斷升級(jí),多核cpu,多線程,多通道等技術(shù)把計(jì)算機(jī)的計(jì)算速度大幅度提升,原來同一時(shí)間只能執(zhí)行一條cpu指令的時(shí)代已經(jīng)過去。隨著多條cpu指令可以并行執(zhí)行的原因,原來不曾出現(xiàn)的資源競(jìng)爭隨著出現(xiàn),在程序中的體現(xiàn)就是隨處可見的多線程環(huán)境。比如要更新數(shù)據(jù)庫的一個(gè)信息,如果沒有并發(fā)控制,多個(gè)線程同時(shí)操作的話,就會(huì)出現(xiàn)互相覆蓋的現(xiàn)象發(fā)生。
鎖要解決的就是資源競(jìng)爭的問題,也就是要把執(zhí)行的指令順序化
隨著互聯(lián)網(wǎng)的興起,現(xiàn)代軟件發(fā)生了翻天覆地的變化,以前單機(jī)的程序,已經(jīng)支撐不了現(xiàn)代的業(yè)務(wù)。無論是在抗壓,還是在高可用等方面都需要多臺(tái)計(jì)算機(jī)協(xié)同工作來解決問題。現(xiàn)代的互聯(lián)網(wǎng)系統(tǒng)都是分布式部署的,分布式部署確實(shí)能帶來性能和效率上的提升,但為此,我們就需要多解決一個(gè)分布式環(huán)境下,數(shù)據(jù)一致性的問題。
當(dāng)某個(gè)資源在多系統(tǒng)之間共享的時(shí)候,為了保證大家訪問這個(gè)資源數(shù)據(jù)是一致的,那么就必須要求在同一時(shí)刻只能被一個(gè)客戶端處理,不能并發(fā)的執(zhí)行,否則就會(huì)出現(xiàn)同一時(shí)刻有人寫有人讀,大家訪問到的數(shù)據(jù)就不一致了。
在分布式系統(tǒng)的時(shí)代,傳統(tǒng)線程之間的鎖機(jī)制,就沒作用了,系統(tǒng)會(huì)有多份并且部署在不同的機(jī)器上,這些資源已經(jīng)不是在線程之間共享了,而是屬于進(jìn)程(服務(wù)器)之間共享的資源。
因此,為了解決這個(gè)問題,我們就必須引入「分布式鎖」。分布式鎖,是指在分布式的部署環(huán)境下,通過鎖機(jī)制來讓多客戶端互斥的對(duì)共享資源進(jìn)行訪問。分布式鎖的特點(diǎn)如下:
1互斥性和我們本地鎖一樣互斥性是最基本,但是分布式鎖需要保證在不同節(jié)點(diǎn)的不同線程的互斥。2可重入性同一個(gè)節(jié)點(diǎn)上的同一個(gè)線程如果獲取了鎖之后那么也可以再次獲取這個(gè)鎖。3鎖超時(shí)和本地鎖一樣支持鎖超時(shí),防止死鎖。4高效,高可用加鎖和解鎖需要高效,同時(shí)也需要保證高可用防止分布式鎖失效,可以增加降級(jí)。5支持阻塞和非阻塞和 ReentrantLock 一樣支持 lock 和 trylock 以及 tryLock(long timeOut)。如果你通過網(wǎng)絡(luò)搜索分布式鎖,最多的就是基于redis的了。基于redis的分布式鎖得益于redis的單線程執(zhí)行機(jī)制,單線程在執(zhí)行上就保證了指令的順序化,所以很大程度上降低了開發(fā)人員的思考設(shè)計(jì)成本。但是,基于redis做分布式鎖難道真的這么容易嗎?
基于redis的分布式鎖常用命令是
SETNX?key?value只在鍵 key 不存在的情況下,將鍵 key的值設(shè)置為value 。若鍵key 已經(jīng)存在, 則SETNX 命令不做任何動(dòng)作。SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡寫。代碼示例:
redis>?SETNX?redislock?"redislock"????(integer)?1
redis>?SETNX?redislock?"redislock2"???
(integer)?0
redis>?GET?redislock???????????????????
"redislock"
????????成功獲取到鎖之后,然后設(shè)置一個(gè)過期時(shí)間(這里避免了客戶端down掉,鎖得不到釋放的問題)
?expire?redislock?5成功拿到鎖的客戶端順利進(jìn)行自己的業(yè)務(wù),業(yè)務(wù)代碼執(zhí)行完,然后再刪除該key
?DEL?redislock如果一切都想想象的那么順利,程序員TMD就不用996了。假如客戶端拿到鎖之后,執(zhí)行設(shè)置超時(shí)指令之前down掉了(現(xiàn)實(shí)總是那么悲劇),那這個(gè)鎖就永遠(yuǎn)都釋放不了.也許你會(huì)想到用 Redis 事務(wù)來解決。但是這里不行,因?yàn)?expire 是依賴于 setnx 的執(zhí)行結(jié)果的,如果 setnx 沒搶到鎖,expire 是不應(yīng)該執(zhí)行的。事務(wù)里沒有 if-else 分支邏輯,事務(wù)的特點(diǎn)是一口氣執(zhí)行,要么全部執(zhí)行要么一個(gè)都不執(zhí)行。公司幾個(gè)億的業(yè)務(wù)又被你耽誤了...
????????以上情況的出現(xiàn)是因?yàn)閮蓚€(gè)命令并非一個(gè)原子性操作,所以在redis 2.8 版本之后出現(xiàn)了新的命令
SETEX?key?seconds?value所以現(xiàn)在可以利用一條原子性操作的命令來獲取鎖
?SETEX?redislock?60?redislockOK
?GET?redislock??#?值
"redislock"
?TTL?redislock??#?剩余生存時(shí)間
(integer)?49
在正常的業(yè)務(wù)當(dāng)中,當(dāng)一個(gè)線程獲取到鎖并且設(shè)置了鎖的過期時(shí)間之后,會(huì)出現(xiàn)由于業(yè)務(wù)代碼執(zhí)行時(shí)間過長,鎖由于到達(dá)超時(shí)時(shí)間自動(dòng)釋放的情況。自動(dòng)釋放之后,其他的線程就會(huì)獲取到分布式鎖,導(dǎo)致業(yè)務(wù)代碼不會(huì)串行執(zhí)行。如果業(yè)務(wù)上允許這樣的情況偶爾發(fā)生,那程序員就開干吧,最后頂多人工干預(yù)一下,update 一下數(shù)據(jù)庫。
為了避免這類情況發(fā)生,在使用redis分布式鎖的時(shí)候,業(yè)務(wù)方應(yīng)盡量避免長時(shí)間執(zhí)行的代碼任務(wù)。
? ? ? ?如果設(shè)置鎖的超時(shí)時(shí)間比較長,在一定程度上可以緩解業(yè)務(wù)代碼執(zhí)行時(shí)間長鎖自動(dòng)到期的問題,但是一旦業(yè)務(wù)代碼down掉,其他等待鎖的線程等待的時(shí)間會(huì)比較長,這種情況下,確保獲取到鎖的程序不會(huì)down 成為了主要問題。
當(dāng)鎖被一個(gè)調(diào)用方獲取之后,其他調(diào)用方在獲取鎖失敗之后,是繼續(xù)輪詢還是直接業(yè)務(wù)失敗呢?如果是繼續(xù)輪詢的話,同步情況下當(dāng)前線程會(huì)一直處于阻塞狀態(tài),所以這里輪詢的情況還是建議使用異步。
可重入性是指已經(jīng)擁有鎖的客戶端再次請(qǐng)求加鎖,如果鎖支持同一個(gè)客戶端重復(fù)加鎖,那么這個(gè)鎖就是可重入的。如果基于redis的分布式鎖要想支持可重入性,需要客戶端封裝,可以使用threadlocal存儲(chǔ)持有鎖的信息。這個(gè)封裝過程會(huì)增加代碼的復(fù)雜度,所以菜菜不推薦這樣做。
如果在多個(gè)客戶端獲取鎖的過程中,redis 掛了怎么辦呢?假如一個(gè)客戶端已經(jīng)獲取到了鎖,這個(gè)時(shí)候redis掛了(假如是redis集群),其他的redis服務(wù)器會(huì)接著提供服務(wù),這個(gè)時(shí)候其他客戶端可以在新的服務(wù)器上獲取到鎖了,這也導(dǎo)致了鎖意義的丟失。有興趣的同學(xué)可以去看看RedLock,這種方案以犧牲性能的代價(jià)解決了這個(gè)問題。
在某些時(shí)候,redis的服務(wù)器時(shí)間發(fā)生的跳躍,由于鎖的過期時(shí)間依賴于服務(wù)器時(shí)間,所以也會(huì)出現(xiàn)兩個(gè)客戶端同時(shí)獲取到鎖的情況發(fā)生。
當(dāng)把以上問題都有解決方案了之后,基于redis的分布式鎖才可以放心使用
基于redis設(shè)計(jì)簡單分布式鎖容易,但是設(shè)計(jì)完美分布式鎖不易, 還覺得基于redis的分布式鎖好做嗎?
架構(gòu)師之路,菜菜與君一起成長
長按識(shí)別二維碼關(guān)注
總結(jié)
以上是生活随笔為你收集整理的程序员修神之路--redis做分布式锁可能不那么简单的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Oracle杀死Java EE:名正言顺
- 下一篇: Abp CLI 上线