注:根據(jù)本人理解叁执,一切分布式協(xié)調(diào)相關(guān)的實(shí)現(xiàn),都需要引入一個(gè)第三者舶掖,如分布式注冊(cè)中心需要依賴(lài)zookeeper球昨、eureka,分布式緩存需要依賴(lài)redis眨攘、memcache等
關(guān)于分布式鎖的概念和應(yīng)用場(chǎng)景本文不作解釋?zhuān)旅鏁?huì)簡(jiǎn)述三種分布式鎖的實(shí)現(xiàn)方式
以下所有實(shí)現(xiàn)方式的demo git地址
jdk的實(shí)現(xiàn)方式
思路:另啟一個(gè)服務(wù)主慰,利用jdk并發(fā)工具來(lái)控制唯一資源嚣州,如在服務(wù)中維護(hù)一個(gè)concurrentHashMap,其他服務(wù)對(duì)某個(gè)key請(qǐng)求鎖時(shí)共螺,通過(guò)該服務(wù)暴露的端口该肴,以網(wǎng)絡(luò)通信的方式發(fā)送消息,服務(wù)端解析這個(gè)消息藐不,將concurrentHashMap中的key對(duì)應(yīng)值設(shè)為true匀哄,分布式鎖請(qǐng)求成功,demo中寫(xiě)了一個(gè)基于netty通信的分布式鎖雏蛮,當(dāng)然你想用java的bio涎嚼、nio或者整合dubbo、spring cloud feign來(lái)實(shí)現(xiàn)通信也沒(méi)問(wèn)題
缺點(diǎn):demo中的鎖寫(xiě)的非常簡(jiǎn)單挑秉,但是要考慮可用性法梯、可靠性、效率犀概、擴(kuò)展性的話立哑,編碼難度會(huì)比較高
redis的實(shí)現(xiàn)方式
原理:利用redis的setnx的特性,能夠保證同一時(shí)間內(nèi)只有一個(gè)請(qǐng)求能夠?qū)ο嗤膋ey執(zhí)行setnx指令姻灶,我們可以理解為只有一個(gè)請(qǐng)求能夠搶到這個(gè)鎖铛绰,由于redis是單線程的,可以非常簡(jiǎn)單地實(shí)現(xiàn)這個(gè)功能产喉。但是這里的鎖的概念與我們平時(shí)鎖理解的有一點(diǎn)區(qū)別捂掰,如mysql中的互斥鎖,行數(shù)據(jù)被鎖定以后镊叁,其他任何線程都無(wú)法對(duì)其進(jìn)行刪改操作尘颓,但是redis里只保證setnx的操作有這個(gè)特性,其他請(qǐng)求依然可以在key被鎖住的情況下晦譬,執(zhí)行del和set等操作,下面是redis分布式鎖的各種實(shí)現(xiàn)方式和缺點(diǎn)互广,按照時(shí)間的發(fā)展排序:
- 1敛腌、直接setnx
直接利用setnx,執(zhí)行完業(yè)務(wù)邏輯后調(diào)用del釋放鎖惫皱,簡(jiǎn)單粗暴
缺點(diǎn):如果setnx成功像樊,還沒(méi)來(lái)得及釋放,服務(wù)掛了旅敷,那么這個(gè)key永遠(yuǎn)都不會(huì)被獲取到 - 2生棍、setnx設(shè)置一個(gè)過(guò)期時(shí)間
為了改正第一個(gè)方法的缺陷,我們用setnx獲取鎖媳谁,然后用expire對(duì)其設(shè)置一個(gè)過(guò)期時(shí)間涂滴,如果服務(wù)掛了友酱,過(guò)期時(shí)間一到自動(dòng)釋放
缺點(diǎn):setnx和expire是兩個(gè)方法,不能保證原子性柔纵,如果在setnx之后缔杉,還沒(méi)來(lái)得及expire,服務(wù)掛了搁料,還是會(huì)出現(xiàn)鎖不釋放的問(wèn)題 - 3或详、set nx px
redis官方為了解決第二種方式存在的缺點(diǎn),在2.8版本為set指令添加了擴(kuò)展參數(shù)nx和ex郭计,保證了setnx+expire的原子性霸琴,使用方法:
set key value ex 5 nx
缺點(diǎn):
①如果在過(guò)期時(shí)間內(nèi),事務(wù)還沒(méi)有執(zhí)行完昭伸,鎖提前被自動(dòng)釋放沈贝,其他的線程還是可以拿到鎖
②上面所說(shuō)的那個(gè)缺點(diǎn)還會(huì)導(dǎo)致當(dāng)前的線程釋放其他線程占有的鎖 - 4、加一個(gè)事務(wù)id
上面所說(shuō)的第一個(gè)缺點(diǎn)勋乾,沒(méi)有特別好的解決方法宋下,只能把過(guò)期時(shí)間盡量設(shè)置的長(zhǎng)一點(diǎn),并且最好不要執(zhí)行耗時(shí)任務(wù)
第二個(gè)缺點(diǎn)辑莫,可以理解為當(dāng)前線程有可能會(huì)釋放其他線程的鎖学歧,那么問(wèn)題就轉(zhuǎn)換為保證線程只能釋放當(dāng)前線程持有的鎖,即setnx的時(shí)候?qū)alue設(shè)為任務(wù)的唯一id各吨,釋放的時(shí)候先get key比較一下value是否與當(dāng)前的id相同枝笨,是則釋放,否則拋異辰已眩回滾横浑,其實(shí)也是變相地解決了第一個(gè)問(wèn)題
缺點(diǎn):get key和將value與id比較是兩個(gè)步驟,不能保證原子性 - 5屉更、set nx px + 事務(wù)id + lua
我們可以用lua來(lái)寫(xiě)一個(gè)getkey并比較的腳本徙融,jedis/luttce/redisson對(duì)lua腳本都有很好的支持
缺點(diǎn):集群環(huán)境下,對(duì)master節(jié)點(diǎn)申請(qǐng)了分布式鎖瑰谜,由于redis的主從同步是異步進(jìn)行的欺冀,master在內(nèi)存中寫(xiě)入了nx之后直接返回,客戶(hù)端獲取鎖成功萨脑,此時(shí)master節(jié)點(diǎn)掛了隐轩,并且數(shù)據(jù)還沒(méi)來(lái)得及同步,另一個(gè)節(jié)點(diǎn)被升級(jí)為master渤早,這樣其他的線程依然可以獲取鎖 - 6职车、redlock
為了解決上面提到的redis集群中的分布式鎖問(wèn)題,redis的作者antirez的提出了red lock的概念,假設(shè)集群中所有的n個(gè)master節(jié)點(diǎn)完全獨(dú)立悴灵,并且沒(méi)有主從同步扛芽,此時(shí)對(duì)所有的節(jié)點(diǎn)都去setnx,并且設(shè)置一個(gè)請(qǐng)求過(guò)期時(shí)間re和鎖的過(guò)期時(shí)間le称勋,同時(shí)re必須小于le(可以理解胸哥,不然請(qǐng)求3秒才拿到鎖,而鎖的過(guò)期時(shí)間只有1秒也太蠢了)赡鲜,此時(shí)如果有n / 2 + 1個(gè)節(jié)點(diǎn)成功拿到鎖空厌,此次分布式鎖就算申請(qǐng)成功
缺點(diǎn):可靠性還沒(méi)有被廣泛驗(yàn)證,并且嚴(yán)重依賴(lài)時(shí)間银酬,好的分布式系統(tǒng)應(yīng)該是異步的嘲更,并不能以時(shí)間為擔(dān)保,程序暫停揩瞪、系統(tǒng)延遲等都可能會(huì)導(dǎo)致時(shí)間錯(cuò)誤(網(wǎng)上還有很多人都對(duì)這個(gè)方法提出了質(zhì)疑赋朦,比如full gc發(fā)生的鎖的正確性問(wèn)題,但是antirez都一一作出了解答李破,感興趣的同學(xué)可以參考一下這位同學(xué)的文章)
基于zookeeper實(shí)現(xiàn)的分布式鎖
- 1宠哄、利用臨時(shí)節(jié)點(diǎn)特性
zookeeper的臨時(shí)節(jié)點(diǎn)有兩個(gè)特性,一是節(jié)點(diǎn)名稱(chēng)不能重復(fù)嗤攻,二是會(huì)隨著客戶(hù)端退出而銷(xiāo)毀毛嫉,因此直接將key作為節(jié)點(diǎn)名稱(chēng),能夠成功創(chuàng)建的客戶(hù)端則獲取成功妇菱,失敗的客戶(hù)端監(jiān)聽(tīng)成功的節(jié)點(diǎn)的刪除事件
缺點(diǎn):所有客戶(hù)端監(jiān)聽(tīng)同一個(gè)節(jié)點(diǎn)承粤,但是同時(shí)只有一個(gè)節(jié)點(diǎn)的事件觸發(fā)是有效的,造成資源的無(wú)效調(diào)度 - 2闯团、利用順序臨時(shí)節(jié)點(diǎn)特性
zookeeper的順序臨時(shí)節(jié)點(diǎn)擁有臨時(shí)節(jié)點(diǎn)的特性辛臊,同時(shí),在一個(gè)父節(jié)點(diǎn)下創(chuàng)建創(chuàng)建的子臨時(shí)順序節(jié)點(diǎn)房交,會(huì)根據(jù)節(jié)點(diǎn)創(chuàng)建的先后順序彻舰,用一個(gè)32位的數(shù)字作為后綴,我們可以用key創(chuàng)建一個(gè)根節(jié)點(diǎn)涌萤,然后每次申請(qǐng)鎖的時(shí)候在其下創(chuàng)建順序節(jié)點(diǎn)淹遵,接著獲取根節(jié)點(diǎn)下所有的順序節(jié)點(diǎn)并排序,獲取順序最小的節(jié)點(diǎn)负溪,如果該節(jié)點(diǎn)的名稱(chēng)與當(dāng)前添加的名稱(chēng)相同,則表示能夠獲取鎖济炎,否則監(jiān)聽(tīng)根節(jié)點(diǎn)下面的處于當(dāng)前節(jié)點(diǎn)之前的節(jié)點(diǎn)的刪除事件川抡,如果監(jiān)聽(tīng)生效,則回到上一步重新判斷順序,直到獲取鎖崖堤。
總結(jié)
- 基于jdk的并發(fā)工具自己實(shí)現(xiàn)的鎖
優(yōu)點(diǎn):不需要引入中間件侍咱,架構(gòu)簡(jiǎn)單
缺點(diǎn):編寫(xiě)一個(gè)可靠、高可用密幔、高效率的分布式鎖服務(wù)楔脯,難度較大 - redis set px nx + 唯一id + lua腳本
優(yōu)點(diǎn):redis本身的讀寫(xiě)性能很高,因此基于redis的分布式鎖效率比較高
缺點(diǎn):依賴(lài)中間件胯甩,分布式環(huán)境下可能會(huì)有節(jié)點(diǎn)數(shù)據(jù)同步問(wèn)題昧廷,可靠性有一定的影響,如果發(fā)生則需要人工介入 - 基于redis的redlock
優(yōu)點(diǎn):可以解決redis集群的同步可用性問(wèn)題
缺點(diǎn):依賴(lài)中間件偎箫,并沒(méi)有被廣泛驗(yàn)證木柬,維護(hù)成本高,需要多個(gè)獨(dú)立的master節(jié)點(diǎn)淹办;需要同時(shí)對(duì)多個(gè)節(jié)點(diǎn)申請(qǐng)鎖眉枕,降低了一些效率 - 基于zookeeper的分布式鎖
優(yōu)點(diǎn):不存在redis的超時(shí)、數(shù)據(jù)同步(zookeeper是同步完以后才返回)怜森、主從切換(zookeeper主從切
換的過(guò)程中服務(wù)是不可用的)的問(wèn)題速挑,可靠性很高
缺點(diǎn):依賴(lài)中間件,保證了可靠性的同時(shí)犧牲了一部分效率(但是依然很高)
沒(méi)有絕對(duì)完美的實(shí)現(xiàn)方式副硅,具體要選擇哪一種分布式鎖姥宝,需要結(jié)合每一種鎖的優(yōu)缺點(diǎn)和業(yè)務(wù)特點(diǎn)而定
redis分布式鎖最佳事件
直接用redisson實(shí)現(xiàn)的分布式鎖,兼容單機(jī)想许、哨兵伶授、集群,利用hash + redis訂閱監(jiān)聽(tīng)實(shí)現(xiàn)重入鎖和鎖的等待流纹。利用list + zSet實(shí)現(xiàn)公平鎖