【转】浅谈分布式锁
前言
隨著互聯(lián)網(wǎng)技術(shù)的不斷發(fā)展,數(shù)據(jù)量的不斷增加,業(yè)務(wù)邏輯日趨復(fù)雜,在這種背景下,傳統(tǒng)的集中式系統(tǒng)已經(jīng)無法滿足我們的業(yè)務(wù)需求,分布式系統(tǒng)被應(yīng)用在更多的場景,而在分布式系統(tǒng)中訪問共享資源就需要一種互斥機(jī)制,來防止彼此之間的互相干擾,以保證一致性,在這種情況下,我們就需要用到分布式鎖。
?
分布式一致性問題
首先我們先來看一個小例子:
假設(shè)某商城有一個商品庫存剩10個,用戶A想要買6個,用戶B想要買5個,在理想狀態(tài)下,用戶A先買走了6了,庫存減少6個還剩4個,此時用戶B應(yīng)該無法購買5個,給出數(shù)量不足的提示;而在真實情況下,用戶A和B同時獲取到商品剩10個,A買走6個,在A更新庫存之前,B又買走了5個,此時B更新庫存,商品還剩5個,這就是典型的電商“秒殺”活動。
從上述例子不難看出,在高并發(fā)情況下,如果不做處理將會出現(xiàn)各種不可預(yù)知的后果。那么在這種高并發(fā)多線程的情況下,解決問題最有效最普遍的方法就是給共享資源或?qū)蚕碣Y源的操作加一把鎖,來保證對資源的訪問互斥。在Java JDK已經(jīng)為我們提供了這樣的鎖,利用ReentrantLcok或者synchronized,即可達(dá)到資源互斥訪問的目的。但是在分布式系統(tǒng)中,由于分布式系統(tǒng)的分布性,即多線程和多進(jìn)程并且分布在不同機(jī)器中,這兩種鎖將失去原有鎖的效果,需要我們自己實現(xiàn)分布式鎖——分布式鎖。
?
分布式鎖需要具備哪些條件
1. 獲取鎖和釋放鎖的性能要好
2. 判斷是否獲得鎖必須是原子性的,否則可能導(dǎo)致多個請求都獲取到鎖
3. 網(wǎng)絡(luò)中斷或宕機(jī)無法釋放鎖時,鎖必須被清楚,不然會發(fā)生死鎖
4. 可重入一個線程中可以多次獲取同一把鎖,比如一個線程在執(zhí)行一個帶鎖的方法,該方法中又調(diào)用了另一個需要相同鎖的方法,則該線程可以直接執(zhí)行調(diào)用的方法,而無需重新獲得鎖;
5.阻塞鎖和非阻塞鎖,阻塞鎖即沒有獲取到鎖,則繼續(xù)等待獲取鎖;非阻塞鎖即沒有獲取到鎖后,不繼續(xù)等待,直接返回鎖失敗。
?
分布式鎖實現(xiàn)方式
一、數(shù)據(jù)庫鎖
1. 基于MySQL鎖表
該實現(xiàn)方式完全依靠數(shù)據(jù)庫唯一索引來實現(xiàn),當(dāng)想要獲得鎖時,即向數(shù)據(jù)庫中插入一條記錄,釋放鎖時就刪除這條記錄。這種方式存在以下幾個問題:
(1) 鎖沒有失效時間,解鎖失敗會導(dǎo)致死鎖,其他線程無法再獲取到鎖,因為唯一索引insert都會返回失敗。
(2) 只能是非阻塞鎖,insert失敗直接就報錯了,無法進(jìn)入隊列進(jìn)行重試
(3) 不可重入,同一線程在沒有釋放鎖之前無法再獲取到鎖
2. 采用樂觀鎖增加版本號
根據(jù)版本號來判斷更新之前有沒有其他線程更新過,如果被更新過,則獲取鎖失敗。
二、緩存鎖
這里我們主要介紹幾種基于redis實現(xiàn)的分布式鎖:
1. 基于setnx、expire兩個命令來實現(xiàn)
基于setnx(set if not exist)的特點(diǎn),當(dāng)緩存里key不存在時,才會去set,否則直接返回false。如果返回true則獲取到鎖,否則獲取鎖失敗,為了防止死鎖,我們再用expire命令對這個key設(shè)置一個超時時間來避免。但是這里看似完美,實則有缺陷,當(dāng)我們setnx成功后,線程發(fā)生異常中斷,expire還沒來的及設(shè)置,那么就會產(chǎn)生死鎖。
解決上述問題有兩種方案
第一種是采用redis2.6.12版本以后的set,它提供了一系列選項
-
EX seconds – 設(shè)置鍵key的過期時間,單位時秒
-
PX milliseconds – 設(shè)置鍵key的過期時間,單位時毫秒
-
NX – 只有鍵key不存在的時候才會設(shè)置key的值
-
XX – 只有鍵key存在的時候才會設(shè)置key的值
第二種采用setnx(),get(),getset()實現(xiàn),大體的實現(xiàn)過程如下:
(1) 線程Asetnx,值為超時的時間戳(t1),如果返回true,獲得鎖。
(2) 線程B用get 命令獲取t1,與當(dāng)前時間戳比較,判斷是否超時,沒超時false,如果已超時執(zhí)行步驟3
(3) 計算新的超時時間t2,使用getset命令返回t3(這個值可能其他線程已經(jīng)修改過),如果t1==t3,獲得鎖,如果t1!=t3說明鎖被其他線程獲取了
(4) 獲取鎖后,處理完業(yè)務(wù)邏輯,再去判斷鎖是否超時,如果沒超時刪除鎖,如果已超時,不用處理(防止刪除其他線程的鎖)
2. RedLock算法
redlock算法是redis作者推薦的一種分布式鎖實現(xiàn)方式,算法的內(nèi)容如下:
(1) 獲取當(dāng)前時間;
(2) 嘗試從5個相互獨(dú)立redis客戶端獲取鎖;
(3) 計算獲取所有鎖消耗的時間,當(dāng)且僅當(dāng)客戶端從多數(shù)節(jié)點(diǎn)獲取鎖,并且獲取鎖的時間小于鎖的有效時間,認(rèn)為獲得鎖;
(4) 重新計算有效期時間,原有效時間減去獲取鎖消耗的時間;
(5) 刪除所有實例的鎖
redlock算法相對于單節(jié)點(diǎn)redis鎖可靠性要更高,但是實現(xiàn)起來條件也較為苛刻。
(1) 必須部署5個節(jié)點(diǎn)才能讓Redlock的可靠性更強(qiáng)。
(2) 需要請求5個節(jié)點(diǎn)才能獲取到鎖,通過Future的方式,先并發(fā)向5個節(jié)點(diǎn)請求,再一起獲得響應(yīng)結(jié)果,能縮短響應(yīng)時間,不過還是比單節(jié)點(diǎn)redis鎖要耗費(fèi)更多時間。
然后由于必須獲取到5個節(jié)點(diǎn)中的3個以上,所以可能出現(xiàn)獲取鎖沖突,即大家都獲得了1-2把鎖,結(jié)果誰也不能獲取到鎖,這個問題,redis作者借鑒了raft算法的精髓,通過沖突后在隨機(jī)時間開始,可以大大降低沖突時間,但是這問題并不能很好的避免,特別是在第一次獲取鎖的時候,所以獲取鎖的時間成本增加了。
如果5個節(jié)點(diǎn)有2個宕機(jī),此時鎖的可用性會極大降低,首先必須等待這兩個宕機(jī)節(jié)點(diǎn)的結(jié)果超時才能返回,另外只有3個節(jié)點(diǎn),客戶端必須獲取到這全部3個節(jié)點(diǎn)的鎖才能擁有鎖,難度也加大了。
如果出現(xiàn)網(wǎng)絡(luò)分區(qū),那么可能出現(xiàn)客戶端永遠(yuǎn)也無法獲取鎖的情況,介于這種情況,下面我們來看一種更可靠的分布式鎖zookeeper鎖。
?
zookeeper分布式鎖
首先我們來了解一下zookeeper的特性,看看它為什么適合做分布式鎖,
zookeeper是一個為分布式應(yīng)用提供一致性服務(wù)的軟件,它內(nèi)部是一個分層的文件系統(tǒng)目錄樹結(jié)構(gòu),規(guī)定統(tǒng)一個目錄下只能有一個唯一文件名。
數(shù)據(jù)模型:
-
永久節(jié)點(diǎn):節(jié)點(diǎn)創(chuàng)建后,不會因為會話失效而消失
-
臨時節(jié)點(diǎn):與永久節(jié)點(diǎn)相反,如果客戶端連接失效,則立即刪除節(jié)點(diǎn)
-
順序節(jié)點(diǎn):與上述兩個節(jié)點(diǎn)特性類似,如果指定創(chuàng)建這類節(jié)點(diǎn)時,zk會自動在節(jié)點(diǎn)名后加一個數(shù)字后綴,并且是有序的。
監(jiān)視器(watcher):
-
當(dāng)創(chuàng)建一個節(jié)點(diǎn)時,可以注冊一個該節(jié)點(diǎn)的監(jiān)視器,當(dāng)節(jié)點(diǎn)狀態(tài)發(fā)生改變時,watch被觸發(fā)時,ZooKeeper將會向客戶端發(fā)送且僅發(fā)送一條通知,因為watch只能被觸發(fā)一次。
根據(jù)zookeeper的這些特性,我們來看看如何利用這些特性來實現(xiàn)分布式鎖:
1. 創(chuàng)建一個鎖目錄lock
2. 希望獲得鎖的線程A就在lock目錄下,創(chuàng)建臨時順序節(jié)點(diǎn)
3. 獲取鎖目錄下所有的子節(jié)點(diǎn),然后獲取比自己小的兄弟節(jié)點(diǎn),如果不存在,則說明當(dāng)前線程順序號最小,獲得鎖
4. 線程B獲取所有節(jié)點(diǎn),判斷自己不是最小節(jié)點(diǎn),設(shè)置監(jiān)聽(watcher)比自己次小的節(jié)點(diǎn)(只關(guān)注比自己次小的節(jié)點(diǎn)是為了防止發(fā)生“羊群效應(yīng)”)
5. 線程A處理完,刪除自己的節(jié)點(diǎn),線程B監(jiān)聽到變更事件,判斷自己是最小的節(jié)點(diǎn),獲得鎖。
?
小結(jié)
在分布式系統(tǒng)中,共享資源互斥訪問問題非常普遍,而針對訪問共享資源的互斥問題,常用的解決方案就是使用分布式鎖,這里只介紹了幾種常用的分布式鎖,分布式鎖的實現(xiàn)方式還有有很多種,根據(jù)業(yè)務(wù)選擇合適的分布式鎖,下面對上述幾種鎖進(jìn)行一下比較:
數(shù)據(jù)庫鎖:
-
優(yōu)點(diǎn):直接使用數(shù)據(jù)庫,使用簡單。
-
缺點(diǎn):分布式系統(tǒng)大多數(shù)瓶頸都在數(shù)據(jù)庫,使用數(shù)據(jù)庫鎖會增加數(shù)據(jù)庫負(fù)擔(dān)。
緩存鎖:
-
優(yōu)點(diǎn):性能高,實現(xiàn)起來較為方便,在允許偶發(fā)的鎖失效情況,不影響系統(tǒng)正常使用,建議采用緩存鎖。
-
缺點(diǎn):通過鎖超時機(jī)制不是十分可靠,當(dāng)線程獲得鎖后,處理時間過長導(dǎo)致鎖超時,就失效了鎖的作用。
zookeeper鎖:
-
優(yōu)點(diǎn):不依靠超時時間釋放鎖;可靠性高;系統(tǒng)要求高可靠性時,建議采用zookeeper鎖。
-
缺點(diǎn):性能比不上緩存鎖,因為要頻繁的創(chuàng)建節(jié)點(diǎn)刪除節(jié)點(diǎn)。
?
“本文轉(zhuǎn)載自 linkedkeeper.com (文/張松然)”
轉(zhuǎn)載于:https://www.cnblogs.com/zdd-java/p/7350437.html
總結(jié)
- 上一篇: 求一个火影好听的名字
- 下一篇: qq个性伤感签名大全