【每日3分鐘技術(shù)干貨 | 面試題+答案 | 分布式鎖篇(一)】
點擊上方“程序員知識碼頭”,選擇“設(shè)為星標(biāo)”
回復(fù)”666“獲取新整理的面試資料
為什么要使用分布式鎖?為了保證一個方法在高并發(fā)情況下的同一時間只能被同一個線程執(zhí)行,在傳統(tǒng)單體應(yīng)用單機部署的情況下,可以使用Java并發(fā)處理相關(guān)的API(如ReentrantLcok或synchronized)進行互斥控制。但是,隨著業(yè)務(wù)發(fā)展的需要,原單體單機部署的系統(tǒng)被演化成分布式系統(tǒng)后,由于分布式系統(tǒng)多線程、多進程并且分布在不同機器上,這將使原單機部署情況下的并發(fā)控制鎖策略失效,為了解決這個問題就需要一種跨JVM的互斥機制來控制共享資源的訪問,這就是分布式鎖要解決的問題。
下面介紹JAVA分布式鎖的三種常用實現(xiàn)方式:
1.基于數(shù)據(jù)庫實現(xiàn)分布式鎖
要實現(xiàn)分布式鎖,最簡單的方式可能就是直接創(chuàng)建一張鎖表,然后通過操作該表中的數(shù)據(jù)來實現(xiàn)了。當(dāng)我們要鎖住某個方法或資源時,我們就在該表中增加一條記錄,想要釋放鎖的時候就刪除這條記錄.
具體操作就是在數(shù)據(jù)庫中創(chuàng)建一個表,表中包含方法名等字段,并在方法名字段上創(chuàng)建唯一索引,想要執(zhí)行某個方法,就使用這個方法名向表中插入數(shù)據(jù),成功插入則獲取鎖,執(zhí)行完成后刪除對應(yīng)的行數(shù)據(jù)釋放鎖。
上面這種簡單的實現(xiàn)有以下幾個問題:
1、這把鎖強依賴數(shù)據(jù)庫的可用性,數(shù)據(jù)庫是一個單點,一旦數(shù)據(jù)庫掛掉,會導(dǎo)致業(yè)務(wù)系統(tǒng)不可用。
2、這把鎖沒有失效時間,一旦解鎖操作失敗,就會導(dǎo)致鎖記錄一直在數(shù)據(jù)庫中,其他線程無法再獲得到鎖。
3、這把鎖只能是非阻塞的,因為數(shù)據(jù)的insert操作,一旦插入失敗就會直接報錯。沒有獲得鎖的線程并不會進入排隊隊列,要想再次獲得鎖就要再次觸發(fā)獲得鎖操作。
4、這把鎖是非重入的,同一個線程在沒有釋放鎖之前無法再次獲得該鎖。因為數(shù)據(jù)中數(shù)據(jù)已經(jīng)存在了。
當(dāng)然,我們也可以有其他方式解決上面的問題。
數(shù)據(jù)庫是單點?搞兩個數(shù)據(jù)庫,數(shù)據(jù)之前雙向同步。一旦掛掉快速切換到備庫上。
沒有失效時間?只要做一個定時任務(wù),每隔一定時間把數(shù)據(jù)庫中的超時數(shù)據(jù)清理一遍。
非阻塞的?搞一個while循環(huán),直到insert成功再返回成功。
非重入的?在數(shù)據(jù)庫表中加個字段,記錄當(dāng)前獲得鎖的機器的主機信息和線程信息,那么下次再獲取鎖的時候先查詢數(shù)據(jù)庫,如果當(dāng)前機器的主機信息和線程信息在數(shù)據(jù)庫可以查到的話,直接把鎖分配給他就可以了。
優(yōu)點:借助數(shù)據(jù)庫,方案簡單。
缺點:在實際實施的過程中會遇到各種不同的問題,為了解決這些問題,實現(xiàn)方式將會越來越復(fù)雜;依賴數(shù)據(jù)庫需要一定的資源開銷,性能問題需要考慮
2.基于Redis實現(xiàn)分布式鎖
在Redis2.6.12版本之前,使用setnx命令設(shè)置key-value、使用expire命令設(shè)置key的過期時間獲取分布式鎖,使用del命令釋放分布式鎖,但是這種實現(xiàn)有如下一些問題:
setnx命令設(shè)置完key-value后,還沒來得及使用expire命令設(shè)置過期時間,當(dāng)前線程掛掉了,會導(dǎo)致當(dāng)前線程設(shè)置的key一直有效,后續(xù)線程無法正常通過setnx獲取鎖,造成死鎖。
出現(xiàn)這個問題是因為兩個命令是分開執(zhí)行并且不具備原子特性,如果能將這兩個命令合二為一就可以解決問題了。在Redis2.6.12版本中實現(xiàn)了這個功能,Redis為set命令增加了一系列選項。也就是說現(xiàn)在set命令就可以實現(xiàn)分布式鎖,下面我們來了解一下set命令(set(keyName, lockValue, "NX", "EX", expireSeconds)):
1. SET命令是原子性操作,NX指令保證只要當(dāng)key不存在時才會設(shè)置value
2. 設(shè)置的value要有唯一性,來確保鎖不會被誤刪(value=系統(tǒng)時間戳+UUID)
3. 當(dāng)上述命令執(zhí)行返回OK時,客戶端獲取鎖成功,否則失敗
4. 客戶端可以通過redis釋放腳本來釋放鎖(del 命令)
5.如果鎖到達(dá)了最大生存時間將會自動釋放
只有當(dāng)前key的value和傳入的value相同才會執(zhí)行DEL命令。
優(yōu)點:高性能,借助Redis實現(xiàn)比較方便。
缺點:線程獲取鎖后,如果處理時間過長會導(dǎo)致鎖超時失效(失效時間我設(shè)置多長時間為好?如何設(shè)置的失效時間太短,方法沒等執(zhí)行完,鎖就自動釋放了,那么就會產(chǎn)生并發(fā)問題。如果設(shè)置的時間太長,其他獲取鎖的線程就可能要平白的多等一段時間。這個問題使用數(shù)據(jù)庫實現(xiàn)分布式鎖同樣存在),所以,通過超時時間來控制鎖的失效時間并不是十分的靠譜。
3.基于Zookeeper實現(xiàn)分布式鎖
ZooKeeper是一個為分布式應(yīng)用提供一致性服務(wù)的開源組件,它內(nèi)部是一個分層的文件系統(tǒng)目錄樹結(jié)構(gòu),規(guī)定同一個目錄下只能有一個唯一文件名。大致思想即為:每個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應(yīng)的指定節(jié)點的目錄下,生成一個唯一的瞬時有序節(jié)點。判斷是否獲取鎖的方式很簡單,只需要判斷有序節(jié)點中序號最小的一個。當(dāng)釋放鎖的時候,只需將這個瞬時節(jié)點刪除即可。同時,其可以避免服務(wù)宕機導(dǎo)致的鎖無法釋放,而產(chǎn)生的死鎖問題。
基于ZooKeeper實現(xiàn)分布式鎖的步驟如下:
創(chuàng)建一個目錄mylock;
線程A想獲取鎖就在mylock目錄下創(chuàng)建臨時順序節(jié)點;
獲取mylock目錄下所有的子節(jié)點,然后獲取比自己小的兄弟節(jié)點,如果不存在,則說明當(dāng)前線程順序號最小,獲得鎖;
線程B獲取所有節(jié)點,判斷自己不是最小節(jié)點,設(shè)置監(jiān)聽比自己次小的節(jié)點;
線程A處理完,刪除自己的節(jié)點,線程B監(jiān)聽到變更事件,判斷自己是不是最小的節(jié)點,如果是則獲得鎖。
優(yōu)點:具備高可用、可重入、阻塞鎖特性,可解決失效死鎖問題。具體說明如下:
鎖無法釋放,造成死鎖!使用Zookeeper可以有效的解決鎖無法釋放的問題,因為在創(chuàng)建鎖的時候,客戶端會在ZK中創(chuàng)建一個臨時節(jié)點,一旦客戶端獲取到鎖之后突然掛掉(Session連接斷開),那么這個臨時節(jié)點就會自動刪除掉。其他客戶端就可以再次獲得鎖。
阻塞鎖特性!使用Zookeeper可以實現(xiàn)阻塞的鎖,客戶端可以通過在ZK中創(chuàng)建順序節(jié)點,并且在節(jié)點上綁定監(jiān)聽器,一旦節(jié)點有變化,Zookeeper會通知客戶端,客戶端可以檢查自己創(chuàng)建的節(jié)點是不是當(dāng)前所有節(jié)點中序號最小的,如果是,那么自己就獲取到鎖,便可以執(zhí)行業(yè)務(wù)邏輯了。
可重入!使用Zookeeper也可以有效的解決不可重入的問題,客戶端在創(chuàng)建節(jié)點的時候,把當(dāng)前客戶端的主機信息和線程信息直接寫入到節(jié)點中,下次想要獲取鎖的時候和當(dāng)前最小的節(jié)點中的數(shù)據(jù)比對一下就可以了。如果和自己的信息一樣,那么自己直接獲取到鎖,如果不一樣就再創(chuàng)建一個臨時的順序節(jié)點,參與排隊。
單點問題?使用Zookeeper可以有效的解決單點問題,ZK是集群部署的,只要集群中有半數(shù)以上的機器存活,就可以對外提供服務(wù)。
缺點:因為需要頻繁的創(chuàng)建和刪除節(jié)點,性能上不如Redis方式。
4.總結(jié)
上面幾種方式,哪種方式都無法做到完美。就像CAP一樣,在復(fù)雜性、可靠性、性能等方面無法同時滿足,所以,根據(jù)不同的應(yīng)用場景選擇最適合自己的才是王道。
結(jié)語
就以這段話自勉、共勉吧。越努力、越幸運,如果你不是官二代、富二代、紅二代,那么請記?。呵趭^才是改變你命運的唯一捷徑。
歡迎在留言區(qū)留下你的觀點,一起討論提高。如果今天的文章讓你有新的啟發(fā),學(xué)習(xí)能力的提升上有新的認(rèn)識,歡迎轉(zhuǎn)發(fā)分享給更多人。
掃描下方“二維碼”,選擇“關(guān)注公眾號”
每天技術(shù)文章第一時間送達(dá)!
關(guān)注「程序員知識碼頭」,收看更多精彩內(nèi)容
