分布式利器Zookeeper(二):分布式鎖

《分布式利器Zookeeper(一)》中對(duì)ZK進(jìn)行了初步的介紹以及搭建ZK集群環(huán)境,本篇博客將涉及的話題是:基于原生API方式操作ZK心铃,Watch機(jī)制钟病,分布式鎖思路探討等。

原生API操作ZK?

什么叫原生API操作ZK呢生蚁?實(shí)際上,利用zookeeper.jar這樣的就是基于原生的API方式操作ZK戏自,因?yàn)檫@個(gè)原生API使用起來并不是讓人很舒服邦投,于是出現(xiàn)了zkclient這種方式,以至到后來基于Curator框架擅笔,讓人使用ZK更加方便志衣。有一句話,Guava is to JAVA what Curator is to Zookeeper猛们。


連接ZK的方式

說明:

在初始化Zookeeper時(shí)念脯,有多種構(gòu)造方法可以選擇,有3個(gè)參數(shù)是必備的:connectionString(多個(gè)ZK SERVER之間以,分隔)弯淘,sessionTimeout(就是zoo.cfg中的tickTime)绿店,Watcher(事件處理通知器)。

需要注意的是ZK的連接是異步的庐橙,因此我們需要CountDownLatch來幫助我們確保ZK初始化完成假勿。

對(duì)于事件(WatchedEvent)而言,有狀態(tài)以及類型态鳖。

事件狀態(tài)及事件類型

下面转培,我們來看一看基于原生API方式的增刪改查:

create

注意,節(jié)點(diǎn)有2大類型浆竭,持久化節(jié)點(diǎn)浸须、臨時(shí)節(jié)點(diǎn)惨寿。在此基礎(chǔ)上,又可以分為持久化順序節(jié)點(diǎn)(PERSISTENT_SEQUENTIAL)删窒、臨時(shí)順序節(jié)點(diǎn)(EPHEMERAL_SEQUENTIAL)裂垦。

節(jié)點(diǎn)類型只支持byte[],也就是說我們是無法直接給一個(gè)對(duì)象給ZK肌索,讓ZK幫助我們完成序列化操作的蕉拢!


get/delete

這里需要注意的是,原生API對(duì)于ZK的操作其實(shí)是分為同步和異步2種方式的驶社。

rc表示return code,就是返回碼测萎,0即為正常亡电。

path是傳入API的參數(shù),ctx也是傳入的參數(shù)硅瞧。

注意在刪除過程中份乒,是需要版本檢查的,所以我們一般提供-1跳過版本檢查機(jī)制腕唧。


Watch機(jī)制

ZK有watch事件或辖,是一次性觸發(fā)的。當(dāng)watch監(jiān)控的數(shù)據(jù)發(fā)生變化枣接,會(huì)通知設(shè)置了該監(jiān)控的client颂暇,即watcher。Zookeeper的watch是有自己的一些特性的:

一次性:請(qǐng)牢記但惶,just watch one time! 因?yàn)閆K的監(jiān)控是一次性的耳鸯,所以每次必須設(shè)置監(jiān)控。

輕量:WatchedEvent是ZK進(jìn)行watch通知的最小單元膀曾,整個(gè)數(shù)據(jù)結(jié)構(gòu)包含:事件狀態(tài)县爬、事件類型、節(jié)點(diǎn)路徑添谊。注意ZK只是通知client節(jié)點(diǎn)的數(shù)據(jù)發(fā)生了變化财喳,而不會(huì)直接提供具體的數(shù)據(jù)內(nèi)容。

客戶端串行執(zhí)行機(jī)制:注意客戶端watch回調(diào)的過程是一個(gè)串行同步的過程斩狱,這為我們保證了順序耳高,我們也應(yīng)該意識(shí)到不能因一個(gè)watch的回調(diào)處理邏輯而影響了整個(gè)客戶端的watch回調(diào)。

下面我們來直接看代碼:

提供Watcher的實(shí)現(xiàn)


提供process方法


main

一定得注意的是所踊,監(jiān)控該節(jié)點(diǎn)和監(jiān)控該節(jié)點(diǎn)的子節(jié)點(diǎn)是2碼子事祝高。

比如exists(path,true)監(jiān)控的就是該path節(jié)點(diǎn)的create/delete/setData;getChildren(path,watcher)監(jiān)控的就是該path節(jié)點(diǎn)下的子節(jié)點(diǎn)的變化(子節(jié)點(diǎn)的創(chuàng)建污筷、修改工闺、刪除都會(huì)監(jiān)控到乍赫,而且事件類型都是一樣的,想一想如何區(qū)分呢陆蟆?給一個(gè)我的思路雷厂,就是我們得先有該path下的子節(jié)點(diǎn)的列表,然后watch觸發(fā)后叠殷,我們對(duì)比下該path下面的子節(jié)點(diǎn)SIZE大小及內(nèi)容改鲫,就知道是增加的是哪個(gè)子節(jié)點(diǎn),刪除的是哪個(gè)子節(jié)點(diǎn)了A质)

getChildren(path,true)和getChildren(path,watcher)有什么區(qū)別像棘?前者是沿用上下文中的Watcher,而后者則是可以設(shè)置一個(gè)新的Watcher的:啊(因此缕题,要想做到一直監(jiān)控,那么就有2種方式胖腾,一個(gè)是注意每次設(shè)置成true烟零,或者干脆每次設(shè)置一個(gè)新的Watcher)

從上面的討論中,你大概能了解到原生的API其實(shí)功能上還不是很強(qiáng)大咸作,有些還得我們?nèi)ゲ傩南前ⅲ胶竺鏋榇蠹医榻BCurator框架,會(huì)有更好的方式進(jìn)行處理记罚。


分布式鎖思路

首先墅诡,我們不談Zookeeper是如何幫助我們處理分布式鎖的,而是先來想一想桐智,什么是分布式鎖书斜?為什么需要分布式鎖?有哪些場(chǎng)景呢酵使?分布式鎖的使用又有哪些注意的荐吉?分布式鎖有什么特性呢?

說起鎖口渔,我們自然想到Java為我們提供的synchronized/Lock样屠,但是這顯然不夠,因?yàn)檫@只能針對(duì)一個(gè)JVM中的多個(gè)線程對(duì)共享資源的操作缺脉。那么對(duì)于多臺(tái)機(jī)器痪欲,多個(gè)進(jìn)程對(duì)同一類資源進(jìn)行操作的話,就是所謂分布式場(chǎng)景下的鎖攻礼。

各個(gè)電商平臺(tái)經(jīng)常搞的“秒殺”活動(dòng)需要對(duì)商品的庫存進(jìn)行保護(hù)业踢、12306火車票也不能多賣,更不允許一張票被多個(gè)人買到礁扮、這樣的場(chǎng)景就需要分布式鎖對(duì)共享資源進(jìn)行保護(hù)知举!

既然瞬沦,Java在分布式場(chǎng)景下的鎖已經(jīng)無能為力,那么我們只能借助其他東西了雇锡!

我們的老朋友:DB

對(duì)逛钻,沒錯(cuò),我們能否借助DB來實(shí)現(xiàn)呢锰提?要知道DB是有一些特點(diǎn)供我們利用的曙痘,比如DB本身就存在鎖機(jī)制(表鎖、行鎖)立肘,唯一約束等等边坤。

假設(shè),我們的DB中有一張表T(id谅年,methodname茧痒,ip,threadname踢故,......)文黎,其中id為主鍵惹苗,methodname為唯一索引殿较。

對(duì)于多臺(tái)機(jī)器,每臺(tái)機(jī)器上的多個(gè)線程而言桩蓉,對(duì)一個(gè)方法method進(jìn)行操作前淋纲,先select下T表中是否存在method這條記錄,如果沒有院究,就插入一條記錄到T中洽瞬。當(dāng)然可能并發(fā)select,但是由于T表的唯一約束业汰,使得只有一個(gè)請(qǐng)求能插入成功伙窃,即獲得鎖。至于釋放鎖样漆,就是方法執(zhí)行完畢后delete這條記錄即可为障。

考慮一些問題:如果DB掛了,怎么辦放祟?如果由于一些因素鳍怨,導(dǎo)致delete沒有執(zhí)行成功,那么這條記錄會(huì)導(dǎo)致該方法再也不能被訪問跪妥!為什么要先select鞋喇,為什么不直接insert呢?性能如何呢眉撵?

為了避免單點(diǎn)侦香,可以主備之間實(shí)現(xiàn)切換落塑;為了避免死鎖的產(chǎn)生,那么我們可以有一個(gè)定時(shí)任務(wù)鄙皇,定期清理T表中的記錄芜赌;先select后insert,其實(shí)是為了保證鎖的可重入性伴逸,也就是說缠沈,如果一臺(tái)IP上的某個(gè)線程獲取了鎖,那么它可以不用在釋放鎖的前提下错蝴,繼續(xù)獲得鎖洲愤;性能上,如果大量的請(qǐng)求顷锰,將會(huì)對(duì)DB考驗(yàn)柬赐,這將成為瓶頸。

到這里官紫,還有一個(gè)明顯的問題肛宋,需要我們考慮:上述的方案,雖然保證了只會(huì)有一個(gè)請(qǐng)求獲得鎖束世,但其他請(qǐng)求都獲取鎖失敗返回了酝陈,而沒有進(jìn)行鎖等待!當(dāng)然毁涉,我們可以通過重試機(jī)制沉帮,來實(shí)現(xiàn)阻塞鎖,不過數(shù)據(jù)庫本身的鎖機(jī)制可以幫助我們完成贫堰。別忘了select ... for update這種阻塞式的行鎖機(jī)制穆壕,commit進(jìn)行鎖的釋放。而且對(duì)于for update這種獨(dú)占鎖其屏,如果長(zhǎng)時(shí)間不提交釋放喇勋,會(huì)一直占用DB連接,連接爆了偎行,就跪了川背!

不說了,老朋友也只能幫我們到這里了睦优!

我們的新朋友:Redis or 其他分布式緩存(Tair/...)

既然說是緩存渗常,相較DB,有更好的性能汗盘;既然說是分布式皱碘,當(dāng)然避免了單點(diǎn)問題;

比如隐孽,用Redis作為分布式鎖的setnx癌椿,這里我就不細(xì)說了健蕊,總之分布式緩存需要特別注意的是緩存的失效時(shí)間。(有效時(shí)間過短踢俄,搞不好業(yè)務(wù)還沒有執(zhí)行完畢缩功,就釋放鎖了;有效時(shí)間過長(zhǎng)都办,其他線程白白等待嫡锌,浪費(fèi)了時(shí)間,拖慢了系統(tǒng)處理速度)

看Zookeeper是如何幫助我們實(shí)現(xiàn)分布式鎖

Zookeeper中臨時(shí)順序節(jié)點(diǎn)的特性:

第一琳钉,節(jié)點(diǎn)的生命周期和client回話綁定势木,即創(chuàng)建節(jié)點(diǎn)的客戶端回話一旦失效,那么這個(gè)節(jié)點(diǎn)就會(huì)被刪除歌懒。(臨時(shí)性)

第二啦桌,每個(gè)父節(jié)點(diǎn)都會(huì)維護(hù)子節(jié)點(diǎn)創(chuàng)建的先后順序,自動(dòng)為子節(jié)點(diǎn)分配一個(gè)整形數(shù)值及皂,以后綴的形式自動(dòng)追加到節(jié)點(diǎn)名稱中甫男,作為這個(gè)節(jié)點(diǎn)最終的節(jié)點(diǎn)名稱。(順序性)

那么验烧,基于臨時(shí)順序節(jié)點(diǎn)的特性板驳,Zookeeper實(shí)現(xiàn)分布式鎖的一般思路如下:

1.client調(diào)用create()方法創(chuàng)建“/root/lock_”節(jié)點(diǎn),注意節(jié)點(diǎn)類型是EPHEMERAL_SEQUENTIAL

2.client調(diào)用getChildren("/root/lock_",watch)來獲取所有已經(jīng)創(chuàng)建的子節(jié)點(diǎn)噪窘,并同時(shí)在這個(gè)節(jié)點(diǎn)上注冊(cè)子節(jié)點(diǎn)變更通知的Watcher

3.客戶端獲取到所有子節(jié)點(diǎn)Path后笋庄,如果發(fā)現(xiàn)自己在步驟1中創(chuàng)建的節(jié)點(diǎn)是所有節(jié)點(diǎn)中最小的效扫,那么就認(rèn)為這個(gè)客戶端獲得了鎖

4.如果在步驟3中倔监,發(fā)現(xiàn)不是最小的,那么等待菌仁,直到下次子節(jié)點(diǎn)變更通知的時(shí)候浩习,在進(jìn)行子節(jié)點(diǎn)的獲取,判斷是否獲取到鎖

5.釋放鎖也比較容易济丘,就是刪除自己創(chuàng)建的那個(gè)節(jié)點(diǎn)即可

上面的這種思路谱秽,在集群規(guī)模很大的情況下,會(huì)出現(xiàn)“羊群效應(yīng)”(Herd Effect):

在上面的分布式鎖的競(jìng)爭(zhēng)中摹迷,有一個(gè)細(xì)節(jié)疟赊,就是在getChildren上注冊(cè)了子節(jié)點(diǎn)變更通知Watcher,這有什么問題么峡碉?這其實(shí)會(huì)導(dǎo)致客戶端大量重復(fù)的運(yùn)行近哟,而且絕大多數(shù)的運(yùn)行結(jié)果都是判斷自己并非是序號(hào)最小的節(jié)點(diǎn),從而繼續(xù)等待下一次通知鲫寄,也就是很多客戶端做了很多無用功吉执。更加要命的是疯淫,在集群規(guī)模很大的情況下,這顯然會(huì)對(duì)Server的性能造成影響戳玫,而且一旦同一個(gè)時(shí)間熙掺,多個(gè)客戶端斷開連接,服務(wù)器會(huì)向其余客戶端發(fā)送大量的事件通知咕宿,這就是所謂的羊群效應(yīng)币绩!

出現(xiàn)這個(gè)問題的根源,其實(shí)在于府阀,上述的思路并沒有找準(zhǔn)客戶端的“痛點(diǎn)”:

客戶端的核心訴求在于判斷自己是否是最小的節(jié)點(diǎn)类浪,所以說每個(gè)節(jié)點(diǎn)的創(chuàng)建者其實(shí)不用關(guān)心所有的節(jié)點(diǎn)變更,它真正關(guān)心的應(yīng)該是比自己序號(hào)小的那個(gè)節(jié)點(diǎn)是否存在肌似!

1.client調(diào)用create()方法創(chuàng)建“/root/lock_”節(jié)點(diǎn)费就,注意節(jié)點(diǎn)類型是EPHEMERAL_SEQUENTIAL

2.client調(diào)用getChildren("/root/lock_",false)來獲取所有已經(jīng)創(chuàng)建的子節(jié)點(diǎn),這里并不注冊(cè)任何Watcher

3.客戶端獲取到所有子節(jié)點(diǎn)Path后川队,如果發(fā)現(xiàn)自己在步驟1中創(chuàng)建的節(jié)點(diǎn)是所有節(jié)點(diǎn)中最小的力细,那么就認(rèn)為這個(gè)客戶端獲得了鎖

4.如果在步驟3中,發(fā)現(xiàn)不是最小的固额,那么找到比自己小的那個(gè)節(jié)點(diǎn)眠蚂,然后對(duì)其調(diào)用exist()方法注冊(cè)事件監(jiān)聽

5.之后一旦這個(gè)被關(guān)注的節(jié)點(diǎn)移除,客戶端會(huì)收到相應(yīng)的通知斗躏,這個(gè)時(shí)候客戶端需要再次調(diào)用getChildren("/root/lock_",false)來確保自己是最小的節(jié)點(diǎn)逝慧,然后進(jìn)入步驟3

OK,talk is cheap show me the code,下一篇文章會(huì)為大家?guī)鞿ookeeper實(shí)現(xiàn)分布式鎖的代碼啄糙。不早啦笛臣,上班去啦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末隧饼,一起剝皮案震驚了整個(gè)濱河市沈堡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌燕雁,老刑警劉巖诞丽,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拐格,居然都是意外死亡僧免,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門捏浊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懂衩,“玉大人,你說我怎么就攤上這事〔眨” “怎么了谒所?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)沛申。 經(jīng)常有香客問我劣领,道長(zhǎng),這世上最難降的妖魔是什么铁材? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任尖淘,我火速辦了婚禮,結(jié)果婚禮上著觉,老公的妹妹穿的比我還像新娘村生。我一直安慰自己,他們只是感情好饼丘,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布趁桃。 她就那樣靜靜地躺著,像睡著了一般肄鸽。 火紅的嫁衣襯著肌膚如雪卫病。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天典徘,我揣著相機(jī)與錄音蟀苛,去河邊找鬼。 笑死逮诲,一個(gè)胖子當(dāng)著我的面吹牛帜平,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梅鹦,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼裆甩,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了帘瞭?” 一聲冷哼從身側(cè)響起淑掌,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蒿讥,失蹤者是張志新(化名)和其女友劉穎蝶念,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芋绸,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡媒殉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了摔敛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片廷蓉。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出桃犬,到底是詐尸還是另有隱情刹悴,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布攒暇,位于F島的核電站土匀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏形用。R本人自食惡果不足惜就轧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望田度。 院中可真熱鬧妒御,春花似錦、人聲如沸镇饺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奸笤。三九已至梦鉴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間揭保,已是汗流浹背肥橙。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秸侣,地道東北人存筏。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像味榛,于是被迫代替她去往敵國(guó)和親椭坚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

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