redis分布式鎖與zk分布式鎖的對比分析
在分布式環(huán)境下,傳統(tǒng)的jvm級別的鎖會失效,那么分布式鎖就是非常有必要的一個技術,一般我們可以通過redis,zk等技術來實現(xiàn)我們的分布式鎖
redis實現(xiàn)分布式鎖
原理
我們都知道redis的處理讀寫請求是單線程的,這種情況就不會發(fā)生并發(fā)的問題,其實實現(xiàn)起來很簡單,就是使用redis的 setnx 命令實現(xiàn),該命令如果redis中存在當前key,就會返回0,否者插入成功。
那么就可以獲取鎖的時候添加一個k-v值(任意的一個值)到redis,釋放鎖的時候就刪除,這樣就使用redis實現(xiàn)了一個分布式鎖,相當于分布式中所謂的鎖概念其實就相當一個redis或者zk中的一個值,有這個值就加鎖成功
能實現(xiàn)的鎖類型
1、普通的鎖
2、讀寫鎖:大致就是給當前的key設置一個特定的值標識當前鎖是讀鎖還是寫鎖,讀與讀之間不互斥,相當與就是沒有鎖,讀與寫,寫于寫之間進行互斥,這個鎖的目的是為了解決緩存一致性的問題,這個問題下面來分析
3、紅鎖:底層原理涉及到redis半數(shù)寫入機制,針對主從架構中主節(jié)點掛了但是數(shù)據(jù)還未同步到從節(jié)點的問題,實現(xiàn)的方式就是當一半以上的節(jié)點都寫入成功了才返回給客戶端成功的提示,而不是主節(jié)點寫入成功就返回,但是這種情況下的效率比較慢
注意事項
1、死鎖的情況:出現(xiàn)死鎖的情況有以下幾種情
(1)應用程序沒有正常的釋放鎖:比如程序拋出異常之類導致釋放鎖代碼沒有執(zhí)行;
解決方案:需要把釋放鎖的代碼寫在finally模塊里面。
(2)鎖還沒有釋放redis宕機:這個時候本來應該刪除的key因為redis服務停掉了導致刪除不成功,出現(xiàn)死鎖的問題
解決方案:給每一個key設置一個超時時間,超時了自動清除。
2、鎖永久失效的情況:出現(xiàn)原因是因為當前線程A還沒有運行完然后鎖因為過期時間的原因自動刪除了,這個時候其他線程B又能拿到這個鎖在redis中創(chuàng)建一個對應的k-v值,然后線程A執(zhí)行到釋放鎖的時候會刪除掉對應key的值,這個時候刪除的值是線程B對應的鎖,而不是線程A的,這樣在高并發(fā)的情況下就有可能導致鎖壓根不生效
解決方案:在進行設值的時候,value值設置成能標識當前線程的一個值,比如在當前線程中創(chuàng)建一個uuid,然后在釋放鎖的時候也要比較value值,相同的情況就表示是當前線程對應的鎖,允許釋放,否則不允許釋放。
3、會存在鎖提前釋放的問題:當然這個問題也是引起上面第2個問題的根本原因,但是解決方案是不一樣的
解決方案:在獲得鎖之后,處理業(yè)務邏輯的過程中,新建一個timer來定時的去重置鎖的生命周期,當然前提是當前業(yè)務邏輯還在執(zhí)行,這個定時的頻率一般設置為鎖生命周期的1/3,redisson中的 **看門狗 **其實內部就是這樣實現(xiàn)。
4、主從結構中鎖丟失:上面 紅鎖 已經(jīng)說明了情況
zk實現(xiàn)分布式鎖
原理
zk實現(xiàn)分布式鎖的原理其實和redis很像,都是往里面插入對應的值,通過zk的create命令來實現(xiàn),zk中的值是通過樹形結構,類似與文件夾的層級目錄一樣,如果當前節(jié)點存在那么create命令就會執(zhí)行失敗,這種情況就代表其他的線程已經(jīng)獲取到了鎖,當前線程通過get -w /xxx的命令對當前鎖進行監(jiān)聽,如果當前鎖被其他線程釋放,那么當前線程會重新參與競爭鎖
能實現(xiàn)的鎖類型
1、非公平鎖:就是通過create創(chuàng)建節(jié)點,誰創(chuàng)建成功誰就獲得了鎖,其他鎖對這個節(jié)點進行監(jiān)聽,當釋放鎖的時候,所有線程又來競爭這個鎖,但是這種情況會引發(fā)羊群效應,就是當一個節(jié)點被釋放的時候所有的線程都會來競爭,浪費性能
2、公平鎖:通過zk的臨時有序節(jié)點來實現(xiàn),當前線程創(chuàng)建一個臨時順序節(jié)點,然后判斷當前節(jié)點是不是最小的節(jié)點,如果時就獲得鎖,如果不是那么就監(jiān)聽他的上一個節(jié)點,等到釋放鎖的時候會通知后一個節(jié)點,然后重復以上判斷,這個就是公平鎖的實現(xiàn)方案,這樣就可以避免羊群效應,減輕服務器的壓力,但是這種情況可能會發(fā)生幽靈節(jié)點的產(chǎn)生導致死鎖
幽靈節(jié)點:就是客戶端發(fā)送創(chuàng)建命令之后,zk已經(jīng)成功創(chuàng)建,但是在響應的時候發(fā)生了宕機,這個時候客戶端以為沒有成功,但是服務器端實際上已經(jīng)有了,但是這個客戶端不知道,就不會去釋放,就造成了幽靈節(jié)點,通過 Protection模式能夠避免這個問題,這個的本質就是在節(jié)點前面加上一個唯一的標識,如uuid,人客戶端再次請求的時候會比較這個uuid,如果有就認為創(chuàng)建成功了,使用curator的protection模式原理就是這樣的,一下附一張公平鎖實現(xiàn)原理圖:
3、讀寫鎖:實現(xiàn)原理和公平鎖差不多,只是在創(chuàng)建每一個節(jié)點的時候標識當前節(jié)點時讀鎖(加read標識)還是寫鎖(加write標識)
兩種鎖的對比
分布式系統(tǒng)中通常要考慮CAP的,一致性,可用性和分區(qū)容錯性,很多場景下是很難同時保證CAP的,這個時候就得做出取舍,分布式鎖也是這樣的。
redis分布式鎖:
- 優(yōu)點:性能高,能保證AP,保證其高可用,
- 缺點:但是不能保證其一致性,原因就是在redis集群+主從的結構中,數(shù)據(jù)是通過分片存儲的,但是這個時候當一個master節(jié)點掛了之后,slave節(jié)點還未同步到master節(jié)點的數(shù)據(jù),導致數(shù)據(jù)丟失,萬一丟失的數(shù)據(jù)剛好是你的鎖,那么就有可能造成并發(fā)問題,所以不能保證強一致性,這種情況下可以通過redisson的紅鎖來解決,解決的原理其實就是redis的半數(shù)寫入機制,但是這樣完全降低了redis的性能,所以一般情況下是不采用的,zk其實能保證其一致性的原因就是其半數(shù)寫入機制加上其 leader選舉的邏輯實現(xiàn)
zk分布式鎖:
- 優(yōu)點:能夠保證其一致性,每個節(jié)點的創(chuàng)建都會同時寫入leader和follwer節(jié)點,半數(shù)以上寫入成功才返回,如果leader節(jié)點掛了之后選舉的流程會優(yōu)先選舉zxid(事務Id)最大的節(jié)點,就是選數(shù)據(jù)最全的,又因為半數(shù)寫入的機制這樣就不會導致丟數(shù)據(jù)(ZAB協(xié)議)
- 缺點:性能沒有redis高
以上已經(jīng)說明了兩種分布式鎖實現(xiàn)的方式及其原理,怎么選擇以實際業(yè)務為準
緩存一致性:數(shù)據(jù)庫于緩存的結果不一樣
雙寫一致性問題(圖網(wǎng)上找的,懶得畫了)
讀寫一致性問題
解決方案:
(1)對于我們的用戶自己的訂單數(shù)據(jù),或者用戶信息數(shù)據(jù),或者說高并發(fā)場景下能容忍短時間的數(shù)據(jù)不一致,這些都可以采用添加過期時間(本來進入到緩存的數(shù)據(jù)就不要求強一致性,這種方式能解決大多數(shù)的情況,這種方式比較推薦也最常用)
(2)延遲雙刪:就是在更新了數(shù)據(jù)庫之后等一小段時間再刪除緩存(這種的缺點就是對于非高并發(fā)的請求,出現(xiàn)緩存一致性的問題概率本來就不大,但是卻人為的降低了代碼的性能)
(3)采用我們redis提供的分布式讀寫鎖,讀讀之間不阻塞,一旦有數(shù)據(jù)更新那么讀的操作就阻塞,等待更新+刪除緩存的操作結束之后再進行讀,這個情況雖然能真正的保證數(shù)據(jù)的一致性,但是加鎖了之后會進行阻塞,高并發(fā)情況下的性能就降低了
(4)使用阿里巴巴的cannal組件,類似于一個mysql的slave節(jié)點,監(jiān)聽著mysql的binlog日志文件,有變化就會主動的通知我們(推薦)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。