zookeeper基本數(shù)據(jù)類型
-
zookeeper是一個(gè)樹形結(jié)構(gòu),類似于前端開發(fā)中的 tree.js 組件;
image.png
zk的數(shù)據(jù)模型也可以理解為 linux/unix 的文件目錄:/usr/local
-
每一個(gè)節(jié)點(diǎn)都稱之為 znode,它可以有子節(jié)點(diǎn),也可以有數(shù)據(jù)
子節(jié)點(diǎn): 就是父目錄下的一個(gè)子目錄,在 zk中稱之為節(jié)點(diǎn),每一個(gè)節(jié)點(diǎn)中都有一些相應(yīng)的數(shù)據(jù),就像目錄下有一些文件數(shù)據(jù)一樣
-
每個(gè)節(jié)點(diǎn)分為臨時(shí)節(jié)點(diǎn)和永久節(jié)點(diǎn),臨時(shí)節(jié)點(diǎn)在客戶端斷開后消失
永久節(jié)點(diǎn):其實(shí)就是一個(gè)持久化的過(guò)程,我們存了一些數(shù)據(jù),只有人為的情況在才會(huì)刪除 如是session超時(shí)或者是session丟失,數(shù)據(jù)還是會(huì)一直存在的。臨時(shí)節(jié)點(diǎn)生命周期依賴創(chuàng)建它的會(huì)話,一旦會(huì)話結(jié)束,臨時(shí)節(jié)點(diǎn)將會(huì)被刪除。臨時(shí)節(jié)點(diǎn)不允許有子節(jié)點(diǎn)。
-
每個(gè)zk節(jié)點(diǎn)都是有各自的版本號(hào),可以通過(guò)命令行來(lái)顯示節(jié)點(diǎn)信息;
節(jié)點(diǎn)的信息其實(shí)就是節(jié)點(diǎn)的詳情,詳情中包含了一些版本號(hào),版本號(hào)是累加的,每當(dāng)節(jié)點(diǎn)中的數(shù)據(jù)發(fā)生變化,版本號(hào)就會(huì)累加(樂(lè)觀鎖)
-
刪除/修改過(guò)時(shí)節(jié)點(diǎn),版本號(hào)不匹配則會(huì)報(bào)錯(cuò)
例如我們?cè)诓樵兡骋粋€(gè)節(jié)點(diǎn)的時(shí)候,比如說(shuō)它的節(jié)點(diǎn)是1,經(jīng)過(guò)兩個(gè)人進(jìn)行刪除或者修改之后,那么它的節(jié)點(diǎn)會(huì)由1變?yōu)?,在變?yōu)?,此時(shí)我們需要去修改或者刪除這個(gè)節(jié)點(diǎn),那么刪除這個(gè)節(jié)點(diǎn)的時(shí)候,我們傳入的版本號(hào)是一個(gè)老的版本號(hào),那么這個(gè)時(shí)候就會(huì)報(bào)一個(gè)版本號(hào)不匹配的異常,這也是在數(shù)據(jù)庫(kù)中是使用樂(lè)觀鎖的一種表現(xiàn)
每一個(gè)zk節(jié)點(diǎn)存儲(chǔ)的數(shù)據(jù)不宜過(guò)大,幾k即可(官方推薦);
-
節(jié)點(diǎn)是可以設(shè)置權(quán)限acl ,可以通過(guò)權(quán)限來(lái)限定用戶的訪問(wèn)
acl:權(quán)限控制列表,后續(xù)會(huì)講到;
zookeeper應(yīng)用場(chǎng)景
Master選舉
在分布式系統(tǒng)中,Master往往用來(lái)協(xié)調(diào)集群中其他系統(tǒng)單元,具有對(duì)分布式系統(tǒng)狀態(tài)變更的決定權(quán),如在讀寫分離的應(yīng)用場(chǎng)景中,客戶端的寫請(qǐng)求往往是由Master來(lái)處理,或者其常常處理一些復(fù)雜的邏輯并將處理結(jié)果同步給其他系統(tǒng)單元。利用Zookeeper的強(qiáng)一致性,能夠很好地保證在分布式高并發(fā)情況下節(jié)點(diǎn)的創(chuàng)建一定能夠保證全局唯一性,即Zookeeper將會(huì)保證客戶端無(wú)法重復(fù)創(chuàng)建一個(gè)已經(jīng)存在的數(shù)據(jù)節(jié)點(diǎn)。
首先創(chuàng)建/master_election/2019-07-05節(jié)點(diǎn),客戶端集群每天會(huì)定時(shí)往該節(jié)點(diǎn)下創(chuàng)建臨時(shí)節(jié)點(diǎn),如/master_election/2019-07-05/binding,這個(gè)過(guò)程中,只有一個(gè)客戶端能夠成功創(chuàng)建,此時(shí)其變成master,其他節(jié)點(diǎn)都會(huì)在節(jié)點(diǎn)/master_election/2019-07-05上注冊(cè)一個(gè)子節(jié)點(diǎn)變更的Watcher,用于監(jiān)控當(dāng)前的Master機(jī)器是否存活,一旦發(fā)現(xiàn)當(dāng)前Master掛了,其余客戶端將會(huì)重新進(jìn)行Master選舉。
這也就是所謂的首腦模式,從而保證我們的集群是高可用的;

數(shù)據(jù)發(fā)布/訂閱(以Dubbo注冊(cè)中心為例)
數(shù)據(jù)發(fā)布/訂閱系統(tǒng),即配置中心。需要發(fā)布者將數(shù)據(jù)發(fā)布到Zookeeper的節(jié)點(diǎn)上,供訂閱者進(jìn)行數(shù)據(jù)訂閱,進(jìn)而達(dá)到動(dòng)態(tài)獲取數(shù)據(jù)的目的,實(shí)現(xiàn)配置信息的集中式管理和數(shù)據(jù)的動(dòng)態(tài)更新。發(fā)布/訂閱一般有兩種設(shè)計(jì)模式:推模式和拉模式,服務(wù)端主動(dòng)將數(shù)據(jù)更新發(fā)送給所有訂閱的客戶端稱為推模式;客戶端主動(dòng)請(qǐng)求獲取最新數(shù)據(jù)稱為拉模式,Zookeeper采用了推拉相結(jié)合的模式,客戶端向服務(wù)端注冊(cè)自己需要關(guān)注的節(jié)點(diǎn),一旦該節(jié)點(diǎn)數(shù)據(jù)發(fā)生變更,那么服務(wù)端就會(huì)向相應(yīng)的客戶端推送Watcher事件通知,客戶端接收到此通知后,主動(dòng)到服務(wù)端獲取最新的數(shù)據(jù)。
若將配置信息存放到Zookeeper上進(jìn)行集中管理,在通常情況下,應(yīng)用在啟動(dòng)時(shí)會(huì)主動(dòng)到Zookeeper服務(wù)端上進(jìn)行一次配置信息的獲取,同時(shí),在指定節(jié)點(diǎn)上注冊(cè)一個(gè)Watcher監(jiān)聽(tīng),這樣在配置信息發(fā)生變更,服務(wù)端都會(huì)實(shí)時(shí)通知所有訂閱的客戶端,從而達(dá)到實(shí)時(shí)獲取最新配置的目的。
Dubbo是集團(tuán)開源的分布式服務(wù)框架,致力于提供高性能和透明化的遠(yuǎn)程服務(wù)調(diào)用解決方案和基于服務(wù)框架展開的完整SOA服務(wù)治理方案。
其中服務(wù)自動(dòng)發(fā)現(xiàn)是最核心的模塊之一,該模塊提供基于注冊(cè)中心的目錄服務(wù),使服務(wù)消費(fèi)方能夠動(dòng)態(tài)的查找服務(wù)提供方,讓服務(wù)地址透明化,同時(shí)服務(wù)提供方可以平滑的對(duì)機(jī)器進(jìn)行擴(kuò)容和縮容,其注冊(cè)中心可以基于提供的外部接口來(lái)實(shí)現(xiàn)各種不同類型的注冊(cè)中心,例如數(shù)據(jù)庫(kù)、ZK和Redis等。接下來(lái)看一下基于ZK實(shí)現(xiàn)的Dubbo注冊(cè)中心。

/dubbo: 這是Dubbo在ZK上創(chuàng)建的根節(jié)點(diǎn)。
/dubbo/com.foo.BarService:這是服務(wù)節(jié)點(diǎn),代表了Dubbo的一個(gè)服務(wù)。
/dubbo/com.foo.BarService/Providers:這是服務(wù)提供者的根節(jié)點(diǎn),其子節(jié)點(diǎn)代表了每個(gè)服務(wù)的真正的提供者。
/dubbo/com.foo.BarService/Comsumers:這是服務(wù)消費(fèi)者的根節(jié)點(diǎn),其子節(jié)點(diǎn)代表了每一個(gè)服務(wù)的真正的消費(fèi)者。
Dubbo基于ZK實(shí)現(xiàn)注冊(cè)中心的工作流程:
服務(wù)提供者:在初始化啟動(dòng)的時(shí)候首先在/dubbo/com.foo.BarService/Providers節(jié)點(diǎn)下創(chuàng)建一個(gè)子節(jié)點(diǎn),同時(shí)寫入自己的URL地址,代表這個(gè)服務(wù)的一個(gè)提供者。
服務(wù)消費(fèi)者 : 在啟動(dòng)的時(shí)候讀圖并訂閱zookeeper上/dubbo/com.foo.BarService/Providers節(jié)點(diǎn)下的所有節(jié)點(diǎn),并解析所有提供者的URL地址作為該服務(wù)類的地址列表,開始發(fā)起正常的調(diào)用。同時(shí)在Consumers節(jié)點(diǎn)下創(chuàng)建一個(gè)臨時(shí)節(jié)點(diǎn),寫入自己的URL地址,代表自己是BarService的一個(gè)消費(fèi)者
監(jiān)控中心 : 監(jiān)控中心是Dubbo服務(wù)治理體系的重要一部分,它需要知道一個(gè)服務(wù)的所有提供者和訂閱者及變化情況。監(jiān)控中心在啟動(dòng)的時(shí)候會(huì)通過(guò)ZK的/dubbo/com.foo.BarService節(jié)點(diǎn)來(lái)獲取所有提供者和消費(fèi)者的url地址,并注冊(cè)Watcher來(lái)監(jiān)聽(tīng)其子節(jié)點(diǎn)變化情況。
所有服務(wù)提供者在ZK上創(chuàng)建的節(jié)點(diǎn)都是臨時(shí)節(jié)點(diǎn),利用的是臨時(shí)節(jié)點(diǎn)的生命周期和客戶端會(huì)話綁定的特性,一旦提供者機(jī)器掛掉無(wú)法對(duì)外提供服務(wù)時(shí)該臨時(shí)節(jié)點(diǎn)就會(huì)從ZK上摘除,這樣服務(wù)消費(fèi)者和監(jiān)控中心都能感知到服務(wù)提供者的變化。
命名服務(wù)
命名服務(wù)也是分布式系統(tǒng)中比較常見(jiàn)的一類場(chǎng)景,被命名的實(shí)體通??梢允羌褐械臋C(jī)器、提供的服務(wù)地址或遠(yuǎn)程對(duì)象,其中較為常見(jiàn)的是一些分布式服務(wù)框架中的服務(wù)地址列表,通過(guò)使用命名服務(wù)客戶端應(yīng)用能夠制定名字來(lái)獲取資源的實(shí)體、服務(wù)地址和提供者的信息等。
上層應(yīng)用使用命名服務(wù)時(shí)可能僅需要一個(gè)全局唯一的名字,類似于數(shù)據(jù)庫(kù)中的唯一主鍵,用數(shù)據(jù)庫(kù)自增id是可以的,但分庫(kù)分表的情況下就無(wú)法依靠數(shù)據(jù)庫(kù)的自增屬性來(lái)唯一標(biāo)識(shí)一條記錄了。另外UUID也是一種廣泛應(yīng)用的ID實(shí)現(xiàn)方式,但如果是用UUID對(duì)服務(wù)進(jìn)行命名的話就太不直觀了,從字面意思根本看不出其表達(dá)的含義。下面看下用ZK如何實(shí)現(xiàn)全局唯一ID的生成。
之前在ZNode介紹時(shí)提過(guò),創(chuàng)建節(jié)點(diǎn)時(shí)可以設(shè)定為SEQUENTIAL順序節(jié)點(diǎn),創(chuàng)建后API會(huì)返回這個(gè)節(jié)點(diǎn)的完整名字,利用這個(gè)特性我們就可以來(lái)生成全局唯一ID了。

所有客戶端根據(jù)自己的任務(wù)類型,在指定類型的任務(wù)下創(chuàng)建一個(gè)順序節(jié)點(diǎn),例如“Job-”節(jié)點(diǎn)
節(jié)點(diǎn)創(chuàng)建完畢后會(huì)返回一個(gè)完整的節(jié)點(diǎn)名稱,如Job-0000000001
客戶端拿到這個(gè)返回值后拼接上type類型,例如type1-Job-000000001,這樣就可以作為一個(gè)全局唯一的ID了
在ZK中每個(gè)數(shù)據(jù)節(jié)點(diǎn)都能維護(hù)一份子節(jié)點(diǎn)的順序序列,當(dāng)客戶端對(duì)其創(chuàng)建一個(gè)順序子節(jié)點(diǎn)時(shí)ZK會(huì)自動(dòng)以后綴的形式在其子節(jié)點(diǎn)上添加一個(gè)序號(hào),該場(chǎng)景就利用了ZK的這個(gè)特性。
分布式鎖
分布式鎖用于控制分布式系統(tǒng)之間同步訪問(wèn)共享資源的一種方式,可以保證不同系統(tǒng)訪問(wèn)一個(gè)或一組資源時(shí)的一致性,主要分為排它鎖和共享鎖。
排它鎖又稱為寫鎖或獨(dú)占鎖,若事務(wù)T1對(duì)數(shù)據(jù)對(duì)象O1加上了排它鎖,那么在整個(gè)加鎖期間,只允許事務(wù)T1對(duì)O1進(jìn)行讀取和更新操作,其他任何事務(wù)都不能再對(duì)這個(gè)數(shù)據(jù)對(duì)象進(jìn)行任何類型的操作,直到T1釋放了排它鎖。
如果不同系統(tǒng)或同一系統(tǒng)不同機(jī)器之間共享了同一資源,那訪問(wèn)這些資源時(shí)通常需要一些互斥手段來(lái)保證一致性,這種情況下就需要用到分布式鎖了。
使用關(guān)系型數(shù)據(jù)庫(kù)是一種簡(jiǎn)單、廣泛的實(shí)現(xiàn)方案,但大多數(shù)大型分布式系統(tǒng)中數(shù)據(jù)庫(kù)已經(jīng)是性能瓶頸了,如果再給數(shù)據(jù)庫(kù)添加額外的鎖會(huì)更加不堪重負(fù);另外,使用數(shù)據(jù)庫(kù)做分布式鎖,當(dāng)搶到鎖的機(jī)器掛掉的話如何釋放鎖也是個(gè)頭疼的問(wèn)題。
接下來(lái)看下使用ZK如何實(shí)現(xiàn)排他鎖。排他鎖的核心是如何保證當(dāng)前有且只有一個(gè)事務(wù)獲得鎖,并且鎖被釋放后所有等待獲取鎖的事務(wù)能夠被通知到。

獲取鎖,在需要獲取排它鎖時(shí),所有客戶端通過(guò)調(diào)用接口,在/exclusive_lock節(jié)點(diǎn)下創(chuàng)建臨時(shí)子節(jié)點(diǎn)/exclusive_lock/lock。Zookeeper可以保證只有一個(gè)客戶端能夠創(chuàng)建成功,沒(méi)有成功的客戶端需要注冊(cè)/exclusive_lock節(jié)點(diǎn)監(jiān)聽(tīng)。
釋放鎖,當(dāng)獲取鎖的客戶端宕機(jī)或者正常完成業(yè)務(wù)邏輯都會(huì)導(dǎo)致臨時(shí)節(jié)點(diǎn)的刪除,此時(shí),所有在/exclusive_lock節(jié)點(diǎn)上注冊(cè)監(jiān)聽(tīng)的客戶端都會(huì)收到通知,可以重新發(fā)起分布式鎖獲取。
共享鎖又稱為讀鎖,若事務(wù)T1對(duì)數(shù)據(jù)對(duì)象O1加上共享鎖,那么當(dāng)前事務(wù)只能對(duì)O1進(jìn)行讀取操作,其他事務(wù)也只能對(duì)這個(gè)數(shù)據(jù)對(duì)象加共享鎖,直到該數(shù)據(jù)對(duì)象上的所有共享鎖都被釋放。

獲取鎖: 在需要獲取共享鎖時(shí),所有客戶端都會(huì)到/shared_lock下面創(chuàng)建一個(gè)臨時(shí)順序節(jié)點(diǎn),如果是讀請(qǐng)求,那么就創(chuàng)建例如/shared_lock/host1-R-00000001的節(jié)點(diǎn),如果是寫請(qǐng)求,那么就創(chuàng)建例如/shared_lock/host2-W-00000002的節(jié)點(diǎn)。
-
判斷讀寫順序:不同事務(wù)可以同時(shí)對(duì)一個(gè)數(shù)據(jù)對(duì)象進(jìn)行讀寫操作,而更新操作必須在當(dāng)前沒(méi)有任何事務(wù)進(jìn)行讀寫情況下進(jìn)行,通過(guò)zookeeper來(lái)確定分布式讀寫順序,大致分為四步。
創(chuàng)建完節(jié)點(diǎn)后,獲取/shared_lock節(jié)點(diǎn)下所有字節(jié)點(diǎn),并對(duì)該節(jié)點(diǎn)變更注冊(cè)監(jiān)聽(tīng)。
確定自己的節(jié)點(diǎn)序號(hào)在所有子節(jié)點(diǎn)中的順序
對(duì)于讀請(qǐng)求:若沒(méi)有比自己序號(hào)小的子節(jié)點(diǎn)或者所有比自己序號(hào)小的子節(jié)點(diǎn)都是讀請(qǐng)求,那么表明自己已經(jīng)成功獲取共享鎖,同時(shí)開始執(zhí)行讀取邏輯,若有寫請(qǐng)求,則需要等待。對(duì)于寫請(qǐng)求:若自己不是序號(hào)最小的子節(jié)點(diǎn),那么需要等待。
接收到Watcher通知后,重復(fù)步驟1.
釋放鎖:其釋放鎖的流程與獨(dú)占鎖的流程一致。
