分布式鎖的幾種實(shí)現(xiàn)方式

一、CAP理論

目前幾乎很多大型網(wǎng)站及應(yīng)用都是分布式部署的趣席,分布式場(chǎng)景中的數(shù)據(jù)一致性問題一直是一個(gè)比較重要的話題商蕴。分布式的CAP理論告訴我們“任何一個(gè)分布式系統(tǒng)都無法同時(shí)滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯(cuò)性(Partition tolerance)夭禽,最多只能同時(shí)滿足兩項(xiàng)或颊≡椅桑”所以传于,很多系統(tǒng)在設(shè)計(jì)之初就要對(duì)這三者做出取舍。在互聯(lián)網(wǎng)領(lǐng)域的絕大多數(shù)的場(chǎng)景中批糟,都需要犧牲強(qiáng)一致性來換取系統(tǒng)的高可用性格了,系統(tǒng)往往只需要保證“最終一致性”,只要這個(gè)最終時(shí)間是在用戶可以接受的范圍內(nèi)即可徽鼎。

二盛末、分布式鎖的幾種實(shí)現(xiàn)方式

在很多場(chǎng)景中,我們?yōu)榱吮WC數(shù)據(jù)的最終一致性否淤,需要很多的技術(shù)方案來支持悄但,比如分布式事務(wù)、分布式鎖等石抡。有的時(shí)候檐嚣,我們需要保證一個(gè)方法在同一時(shí)間內(nèi)只能被同一個(gè)線程執(zhí)行。在單機(jī)環(huán)境中啰扛,Java中其實(shí)提供了很多并發(fā)處理相關(guān)的API嚎京,但是這些API在分布式場(chǎng)景中就無能為力了。也就是說單純的Java Api并不能提供分布式鎖的能力隐解。所以針對(duì)分布式鎖的實(shí)現(xiàn)目前有多種方案鞍帝。

針對(duì)分布式鎖的實(shí)現(xiàn),目前比較常用的有以下幾種方案:
基于數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖煞茫、基于緩存(redis帕涌,memcached,tair)實(shí)現(xiàn)分布式鎖续徽、 基于Zookeeper實(shí)現(xiàn)分布式鎖

在分析這幾種實(shí)現(xiàn)方案之前我們先來想一下蚓曼,我們需要的分布式鎖應(yīng)該是怎么樣的?(這里以方法鎖為例钦扭,資源鎖同理)

  • 可以保證在分布式部署的應(yīng)用集群中纫版,同一個(gè)方法在同一時(shí)間只能被一臺(tái)機(jī)器上的一個(gè)線程執(zhí)行。
  • 這把鎖要是一把可重入鎖(避免死鎖)
  • 這把鎖最好是一把阻塞鎖(根據(jù)業(yè)務(wù)需求考慮要不要這條)
  • 有高可用的獲取鎖和釋放鎖功能
  • 獲取鎖和釋放鎖的性能要好

三土全、基于數(shù)據(jù)庫實(shí)現(xiàn)的分布式鎖

基于數(shù)據(jù)庫表
要實(shí)現(xiàn)分布式鎖捎琐,最簡(jiǎn)單的方式可能就是直接創(chuàng)建一張鎖表,然后通過操作該表中的數(shù)據(jù)來實(shí)現(xiàn)了裹匙。
當(dāng)我們要鎖住某個(gè)方法或資源時(shí),我們就在該表中增加一條記錄末秃,想要釋放鎖的時(shí)候就刪除這條記錄概页。

上面這種簡(jiǎn)單的實(shí)現(xiàn)有以下幾個(gè)問題:

  • 這把鎖強(qiáng)依賴數(shù)據(jù)庫的可用性,數(shù)據(jù)庫是一個(gè)單點(diǎn)练慕,一旦數(shù)據(jù)庫掛掉惰匙,會(huì)導(dǎo)致業(yè)務(wù)系統(tǒng)不可用技掏。
  • 這把鎖沒有失效時(shí)間,一旦解鎖操作失敗项鬼,就會(huì)導(dǎo)致鎖記錄一直在數(shù)據(jù)庫中哑梳,其他線程無法再獲得到鎖。
  • 這把鎖只能是非阻塞的绘盟,因?yàn)閿?shù)據(jù)的insert操作鸠真,一旦插入失敗就會(huì)直接報(bào)錯(cuò)。沒有獲得鎖的線程并不會(huì)進(jìn)入排隊(duì)隊(duì)列龄毡,要想再次獲得鎖就要再次觸發(fā)獲得鎖操作吠卷。
  • 這把鎖是非重入的,同一個(gè)線程在沒有釋放鎖之前無法再次獲得該鎖沦零。因?yàn)閿?shù)據(jù)中數(shù)據(jù)已經(jīng)存在了祭隔。

當(dāng)然,我們也可以有其他方式解決上面的問題路操。

  • 數(shù)據(jù)庫是單點(diǎn)疾渴?搞兩個(gè)數(shù)據(jù)庫,數(shù)據(jù)之前雙向同步屯仗。一旦掛掉快速切換到備庫上搞坝。
  • 沒有失效時(shí)間?只要做一個(gè)定時(shí)任務(wù)祭钉,每隔一定時(shí)間把數(shù)據(jù)庫中的超時(shí)數(shù)據(jù)清理一遍瞄沙。
  • 非阻塞的?搞一個(gè)while循環(huán)慌核,直到insert成功再返回成功距境。
  • 非重入的?在數(shù)據(jù)庫表中加個(gè)字段垮卓,記錄當(dāng)前獲得鎖的機(jī)器的主機(jī)信息和線程信息垫桂,那么下次再獲取鎖的時(shí)候先查詢數(shù)據(jù)庫,如果當(dāng)前機(jī)器的主機(jī)信息和線程信息在數(shù)據(jù)庫可以查到的話粟按,直接把鎖分配給他就可以了诬滩。

基于數(shù)據(jù)庫排他鎖
除了可以通過增刪操作數(shù)據(jù)表中的記錄以外,其實(shí)還可以借助數(shù)據(jù)中自帶的鎖來實(shí)現(xiàn)分布式的鎖灭将。

我們還用剛剛創(chuàng)建的那張數(shù)據(jù)庫表疼鸟。可以通過數(shù)據(jù)庫的排他鎖來實(shí)現(xiàn)分布式鎖庙曙。 基于MySql的InnoDB引擎空镜,可以使用以下方法來實(shí)現(xiàn)加鎖操作:


image.png

在查詢語句后面增加for update,數(shù)據(jù)庫會(huì)在查詢過程中給數(shù)據(jù)庫表增加排他鎖(這里再多提一句,InnoDB引擎在加鎖的時(shí)候吴攒,只有通過索引進(jìn)行檢索的時(shí)候才會(huì)使用行級(jí)鎖张抄,否則會(huì)使用表級(jí)鎖。這里我們希望使用行級(jí)鎖洼怔,就要給method_name添加索引署惯,值得注意的是,這個(gè)索引一定要?jiǎng)?chuàng)建成唯一索引镣隶,否則會(huì)出現(xiàn)多個(gè)重載方法之間無法同時(shí)被訪問的問題极谊。重載方法的話建議把參數(shù)類型也加上。)矾缓。當(dāng)某條記錄被加上排他鎖之后怀酷,其他線程無法再在該行記錄上增加排他鎖。

我們可以認(rèn)為獲得排它鎖的線程即可獲得分布式鎖嗜闻,當(dāng)獲取到鎖之后蜕依,可以執(zhí)行方法的業(yè)務(wù)邏輯,執(zhí)行完方法之后琉雳,再通過以下方法解鎖:


image.png

通過connection.commit()操作來釋放鎖样眠。

這種方法可以有效的解決上面提到的無法釋放鎖和阻塞鎖的問題。

  • 阻塞鎖翠肘? for update語句會(huì)在執(zhí)行成功后立即返回檐束,在執(zhí)行失敗時(shí)一直處于阻塞狀態(tài),直到成功束倍。
  • 鎖定之后服務(wù)宕機(jī)祖驱,無法釋放兵扬?使用這種方式肛循,服務(wù)宕機(jī)之后數(shù)據(jù)庫會(huì)自己把鎖釋放掉走净。

但是還是無法直接解決數(shù)據(jù)庫單點(diǎn)和可重入問題。

這里還可能存在另外一個(gè)問題邮旷,雖然我們對(duì)method_name 使用了唯一索引黄选,并且顯示使用for update來使用行級(jí)鎖。但是婶肩,MySql會(huì)對(duì)查詢進(jìn)行優(yōu)化办陷,即便在條件中使用了索引字段,但是否使用索引來檢索數(shù)據(jù)是由 MySQL 通過判斷不同執(zhí)行計(jì)劃的代價(jià)來決定的律歼,如果 MySQL 認(rèn)為全表掃效率更高民镜,比如對(duì)一些很小的表,它就不會(huì)使用索引险毁,這種情況下 InnoDB 將使用表鎖殃恒,而不是行鎖植旧。如果發(fā)生這種情況就悲劇了辱揭。离唐。。

還有一個(gè)問題问窃,就是我們要使用排他鎖來進(jìn)行分布式鎖的lock亥鬓,那么一個(gè)排他鎖長(zhǎng)時(shí)間不提交,就會(huì)占用數(shù)據(jù)庫連接域庇。一旦類似的連接變得多了嵌戈,就可能把數(shù)據(jù)庫連接池?fù)伪?/p>

總結(jié)
總結(jié)一下使用數(shù)據(jù)庫來實(shí)現(xiàn)分布式鎖的方式,這兩種方式都是依賴數(shù)據(jù)庫的一張表听皿,一種是通過表中的記錄的存在情況確定當(dāng)前是否有鎖存在熟呛,另外一種是通過數(shù)據(jù)庫的排他鎖來實(shí)現(xiàn)分布式鎖。
數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖的優(yōu)點(diǎn):直接借助數(shù)據(jù)庫尉姨,容易理解庵朝。

數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖的缺點(diǎn):

  • 會(huì)有各種各樣的問題,在解決問題的過程中會(huì)使整個(gè)方案變得越來越復(fù)雜又厉。
  • 操作數(shù)據(jù)庫需要一定的開銷九府,性能問題需要考慮。
  • 使用數(shù)據(jù)庫的行級(jí)鎖并不一定靠譜覆致,尤其是當(dāng)我們的鎖表并不大的時(shí)候侄旬。

四、基于緩存實(shí)現(xiàn)分布式鎖

相比較于基于數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖的方案來說煌妈,基于緩存來實(shí)現(xiàn)在性能方面會(huì)表現(xiàn)的更好一點(diǎn)儡羔。而且很多緩存是可以集群部署的,可以解決單點(diǎn)問題璧诵。
目前有很多成熟的緩存產(chǎn)品汰蜘,包括Redis,memcached以及Tair腮猖。
這里以Tair為例來分析下使用緩存實(shí)現(xiàn)分布式鎖的方案鉴扫。關(guān)于Redis和memcached在網(wǎng)絡(luò)上有很多相關(guān)的文章,并且也有一些成熟的框架及算法可以直接使用澈缺。
基于Tair的實(shí)現(xiàn)分布式鎖其實(shí)和Redis類似坪创,其中主要的實(shí)現(xiàn)方式是使用TairManager.put方法來實(shí)現(xiàn)。

總結(jié)
可以使用緩存來代替數(shù)據(jù)庫來實(shí)現(xiàn)分布式鎖姐赡,這個(gè)可以提供更好的性能莱预,同時(shí),很多緩存服務(wù)都是集群部署的项滑,可以避免單點(diǎn)問題依沮。并且很多緩存服務(wù)都提供了可以用來實(shí)現(xiàn)分布式鎖的方法,比如Tair的put方法,redis的setnx方法等危喉。并且宋渔,這些緩存服務(wù)也都提供了對(duì)數(shù)據(jù)的過期自動(dòng)刪除的支持,可以直接設(shè)置超時(shí)時(shí)間來控制鎖的釋放辜限。

使用緩存實(shí)現(xiàn)分布式鎖的優(yōu)點(diǎn):

  • 性能好皇拣,實(shí)現(xiàn)起來較為方便。
  • 使用緩存實(shí)現(xiàn)分布式鎖的缺點(diǎn)
  • 通過超時(shí)時(shí)間來控制鎖的失效時(shí)間并不是十分的靠譜薄嫡。

五氧急、基于Zookeeper實(shí)現(xiàn)分布式鎖

基于zookeeper臨時(shí)有序節(jié)點(diǎn)可以實(shí)現(xiàn)的分布式鎖。

大致思想即為:每個(gè)客戶端對(duì)某個(gè)方法加鎖時(shí)毫深,在zookeeper上的與該方法對(duì)應(yīng)的指定節(jié)點(diǎn)的目錄下吩坝,生成一個(gè)唯一的瞬時(shí)有序節(jié)點(diǎn)。 判斷是否獲取鎖的方式很簡(jiǎn)單哑蔫,只需要判斷有序節(jié)點(diǎn)中序號(hào)最小的一個(gè)钉寝。 當(dāng)釋放鎖的時(shí)候,只需將這個(gè)瞬時(shí)節(jié)點(diǎn)刪除即可鸳址。同時(shí)瘩蚪,其可以避免服務(wù)宕機(jī)導(dǎo)致的鎖無法釋放,而產(chǎn)生的死鎖問題稿黍。

來看下Zookeeper能不能解決前面提到的問題疹瘦。

  • 鎖無法釋放?使用Zookeeper可以有效的解決鎖無法釋放的問題巡球,因?yàn)樵趧?chuàng)建鎖的時(shí)候言沐,客戶端會(huì)在ZK中創(chuàng)建一個(gè)臨時(shí)節(jié)點(diǎn),一旦客戶端獲取到鎖之后突然掛掉(Session連接斷開)酣栈,那么這個(gè)臨時(shí)節(jié)點(diǎn)就會(huì)自動(dòng)刪除掉险胰。其他客戶端就可以再次獲得鎖。
  • 非阻塞鎖矿筝?使用Zookeeper可以實(shí)現(xiàn)阻塞的鎖起便,客戶端可以通過在ZK中創(chuàng)建順序節(jié)點(diǎn),并且在節(jié)點(diǎn)上綁定監(jiān)聽器窖维,一旦節(jié)點(diǎn)有變化榆综,Zookeeper會(huì)通知客戶端,客戶端可以檢查自己創(chuàng)建的節(jié)點(diǎn)是不是當(dāng)前所有節(jié)點(diǎn)中序號(hào)最小的铸史,如果是鼻疮,那么自己就獲取到鎖,便可以執(zhí)行業(yè)務(wù)邏輯了琳轿。
  • 不可重入判沟?使用Zookeeper也可以有效的解決不可重入的問題耿芹,客戶端在創(chuàng)建節(jié)點(diǎn)的時(shí)候,把當(dāng)前客戶端的主機(jī)信息和線程信息直接寫入到節(jié)點(diǎn)中挪哄,下次想要獲取鎖的時(shí)候和當(dāng)前最小的節(jié)點(diǎn)中的數(shù)據(jù)比對(duì)一下就可以了吧秕。如果和自己的信息一樣,那么自己直接獲取到鎖中燥,如果不一樣就再創(chuàng)建一個(gè)臨時(shí)的順序節(jié)點(diǎn)寇甸,參與排隊(duì)。
  • 單點(diǎn)問題疗涉?使用Zookeeper可以有效的解決單點(diǎn)問題,ZK是集群部署的吟秩,只要集群中有半數(shù)以上的機(jī)器存活咱扣,就可以對(duì)外提供服務(wù)。

可以直接使用zookeeper第三方庫Curator客戶端涵防,這個(gè)客戶端中封裝了一個(gè)可重入的鎖服務(wù)闹伪。

image.png

Curator提供的InterProcessMutex是分布式鎖的實(shí)現(xiàn)。acquire方法用戶獲取鎖壮池,release方法用于釋放鎖偏瓤。

使用ZK實(shí)現(xiàn)的分布式鎖好像完全符合了本文開頭我們對(duì)一個(gè)分布式鎖的所有期望。但是椰憋,其實(shí)并不是厅克,Zookeeper實(shí)現(xiàn)的分布式鎖其實(shí)存在一個(gè)缺點(diǎn),那就是性能上可能并沒有緩存服務(wù)那么高橙依。因?yàn)槊看卧趧?chuàng)建鎖和釋放鎖的過程中证舟,都要?jiǎng)討B(tài)創(chuàng)建、銷毀瞬時(shí)節(jié)點(diǎn)來實(shí)現(xiàn)鎖功能窗骑。ZK中創(chuàng)建和刪除節(jié)點(diǎn)只能通過Leader服務(wù)器來執(zhí)行女责,然后將數(shù)據(jù)同步到所有的Follower機(jī)器上。

其實(shí)创译,使用Zookeeper也有可能帶來并發(fā)問題抵知,只是并不常見而已∪碜澹考慮這樣的情況刷喜,由于網(wǎng)絡(luò)抖動(dòng),客戶端可ZK集群的session連接斷了互订,那么zk以為客戶端掛了吱肌,就會(huì)刪除臨時(shí)節(jié)點(diǎn),這時(shí)候其他客戶端就可以獲取到分布式鎖了仰禽。就可能產(chǎn)生并發(fā)問題氮墨。這個(gè)問題不常見是因?yàn)閦k有重試機(jī)制纺蛆,一旦zk集群檢測(cè)不到客戶端的心跳,就會(huì)重試规揪,Curator客戶端支持多種重試策略桥氏。多次重試之后還不行的話才會(huì)刪除臨時(shí)節(jié)點(diǎn)。(所以猛铅,選擇一個(gè)合適的重試策略也比較重要字支,要在鎖的粒度和并發(fā)之間找一個(gè)平衡。)

總結(jié)
使用Zookeeper實(shí)現(xiàn)分布式鎖的優(yōu)點(diǎn):

  • 有效的解決單點(diǎn)問題奸忽,不可重入問題堕伪,非阻塞問題以及鎖無法釋放的問題。實(shí)現(xiàn)起來較為簡(jiǎn)單栗菜。
  • 使用Zookeeper實(shí)現(xiàn)分布式鎖的缺點(diǎn):
  • 性能上不如使用緩存實(shí)現(xiàn)分布式鎖欠雌。 需要對(duì)ZK的原理有所了解。

六疙筹、三種方案的比較

上面幾種方式富俄,哪種方式都無法做到完美。就像CAP一樣而咆,在復(fù)雜性霍比、可靠性、性能等方面無法同時(shí)滿足暴备,所以悠瞬,根據(jù)不同的應(yīng)用場(chǎng)景選擇最適合自己的才是王道。

從理解的難易程度角度(從低到高)
數(shù)據(jù)庫 > 緩存 > Zookeeper
從實(shí)現(xiàn)的復(fù)雜性角度(從低到高)
Zookeeper >= 緩存 > 數(shù)據(jù)庫
從性能角度(從高到低)
緩存 > Zookeeper >= 數(shù)據(jù)庫
從可靠性角度(從高到低)
Zookeeper > 緩存 > 數(shù)據(jù)庫

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末馍驯,一起剝皮案震驚了整個(gè)濱河市阁危,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汰瘫,老刑警劉巖狂打,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異混弥,居然都是意外死亡趴乡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門蝗拿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晾捏,“玉大人,你說我怎么就攤上這事哀托〉胄粒” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵仓手,是天一觀的道長(zhǎng)胖齐。 經(jīng)常有香客問我玻淑,道長(zhǎng),這世上最難降的妖魔是什么呀伙? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任补履,我火速辦了婚禮,結(jié)果婚禮上剿另,老公的妹妹穿的比我還像新娘箫锤。我一直安慰自己,他們只是感情好雨女,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布谚攒。 她就那樣靜靜地躺著,像睡著了一般戚篙。 火紅的嫁衣襯著肌膚如雪五鲫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天岔擂,我揣著相機(jī)與錄音,去河邊找鬼浪耘。 笑死乱灵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的七冲。 我是一名探鬼主播痛倚,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼澜躺!你這毒婦竟也來了蝉稳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤掘鄙,失蹤者是張志新(化名)和其女友劉穎耘戚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體操漠,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡收津,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浊伙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撞秋。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嚣鄙,靈堂內(nèi)的尸體忽然破棺而出吻贿,到底是詐尸還是另有隱情,我是刑警寧澤哑子,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布舅列,位于F島的核電站肌割,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏剧蹂。R本人自食惡果不足惜声功,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宠叼。 院中可真熱鬧先巴,春花似錦、人聲如沸冒冬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽简烤。三九已至剂邮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間横侦,已是汗流浹背挥萌。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枉侧,地道東北人引瀑。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像榨馁,于是被迫代替她去往敵國(guó)和親憨栽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容