不懂 ZooKeeper?没关系,这一篇给你讲的明明白白
前言
面試常常被要求「熟悉分布式技術(shù)」,當(dāng)年搞 “XXX管理系統(tǒng)” 的時(shí)候,我都不知道分布式系統(tǒng)是個(gè)啥。分布式系統(tǒng)是一個(gè)硬件或軟件組件分布在不同的網(wǎng)絡(luò)計(jì)算機(jī)中上,彼此之間僅僅通過消息傳遞進(jìn)行通信和協(xié)調(diào)的系統(tǒng)。
計(jì)算機(jī)系統(tǒng)從集中式到分布式的變革伴隨著包括分布式網(wǎng)絡(luò)、分布式事務(wù)、分布式數(shù)據(jù)一致性等在內(nèi)的一系列問題和挑戰(zhàn),同時(shí)也催生了一大批諸如ACID、CAP和 BASE 等經(jīng)典理論的快速發(fā)展。
為了解決分布式一致性問題,涌現(xiàn)出了一大批經(jīng)典的一致性協(xié)議和算法,最為著名的就是二階段提交協(xié)議(2PC),三階段提交協(xié)議(3PC)和Paxos算法。Zookeeper的一致性是通過基于 Paxos 算法的 ZAB 協(xié)議完成的。
1. 概述
1.1 定義
ZooKeeper 官網(wǎng)是這么介紹的:”Apache ZooKeeper 致力于開發(fā)和維護(hù)一個(gè)支持高度可靠的分布式協(xié)調(diào)的開源服務(wù)器“
1.2 ZooKeeper是個(gè)啥
ZooKeeper 是 Apache 軟件基金會(huì)的一個(gè)軟件項(xiàng)目,它為大型「分布式計(jì)算」提供開源的分布式配置服務(wù)、同步服務(wù)和命名注冊。
Zookeeper 最早起源于雅虎研究院的一個(gè)研究小組。在當(dāng)時(shí),研究人員發(fā)現(xiàn),在雅虎內(nèi)部很多大型系統(tǒng)基本都需要依賴一個(gè)類似的系統(tǒng)來進(jìn)行分布式協(xié)調(diào),但是這些系統(tǒng)往往都存在分布式單點(diǎn)問題。所以,雅虎的開發(fā)人員就試圖開發(fā)一個(gè)通用的無單點(diǎn)問題的分布式協(xié)調(diào)框架,以便讓開發(fā)人員將精力集中在處理業(yè)務(wù)邏輯上,Zookeeper 就這樣誕生了。后來捐贈(zèng)給了 Apache ,現(xiàn)已成為 Apache 頂級項(xiàng)目。
關(guān)于“ZooKeeper”這個(gè)項(xiàng)目的名字,其實(shí)也有一段趣聞。在立項(xiàng)初期,考慮到之前內(nèi)部很多項(xiàng)目都是使用動(dòng)物的名字來命名的(例如著名的Pig項(xiàng)目),雅虎的工程師希望給這個(gè)項(xiàng)目也取一個(gè)動(dòng)物的名字。時(shí)任研究院的首席科學(xué)家 RaghuRamakrishnan 開玩笑地說:“再這樣下去,我們這兒就變成動(dòng)物園了!”此話一出,大家紛紛表示就叫動(dòng)物園管理員吧一一一因?yàn)楦鱾€(gè)以動(dòng)物命名的分布式組件放在一起,雅虎的整個(gè)分布式系統(tǒng)看上去就像一個(gè)大型的動(dòng)物園了,而 Zookeeper 正好要用來進(jìn)行分布式環(huán)境的協(xié)調(diào)一一于是,Zookeeper 的名字也就由此誕生了。
ZooKeeper 是用于維護(hù)配置信息,命名,提供分布式同步和提供組服務(wù)的集中式服務(wù)。所有這些類型的服務(wù)都以某種形式被分布式應(yīng)用程序使用。每次實(shí)施它們時(shí),都會(huì)進(jìn)行很多工作來修復(fù)不可避免的 bug 和競爭條件。由于難以實(shí)現(xiàn)這類服務(wù),因此應(yīng)用程序最初通常會(huì)跳過它們,這會(huì)使它們在存在更改的情況下變得脆弱并且難以管理。即使部署正確,這些服務(wù)的不同實(shí)現(xiàn)也會(huì)導(dǎo)致管理復(fù)雜。
ZooKeeper 的目標(biāo)是將這些不同服務(wù)的精華提煉為一個(gè)非常簡單的接口,用于集中協(xié)調(diào)服務(wù)。服務(wù)本身是分布式的,并且高度可靠。服務(wù)將實(shí)現(xiàn)共識(shí),組管理和狀態(tài)協(xié)議,因此應(yīng)用程序不需要自己實(shí)現(xiàn)它們。
1.3 ZooKeeper工作機(jī)制
ZooKeeper 從設(shè)計(jì)模式角度來理解:就是一個(gè)基于觀察者模式設(shè)計(jì)的分布式服務(wù)管理框架,它負(fù)責(zé)存儲(chǔ)和管理大家都關(guān)心的數(shù)據(jù),然后接受觀察者的注冊,一旦這些數(shù)據(jù)的狀態(tài)發(fā)生變化,ZK 就將負(fù)責(zé)通知已經(jīng)在 ZK 上注冊的那些觀察者做出相應(yīng)的反應(yīng),從而實(shí)現(xiàn)集群中類似 Master/Slave 管理模式。
1.4 特性
圖片來源:官網(wǎng)wiki
ZooKeeper:一個(gè)領(lǐng)導(dǎo)者(leader),多個(gè)跟隨者(follower)組成的集群。
Leader 負(fù)責(zé)進(jìn)行投票的發(fā)起和決議,更新系統(tǒng)狀態(tài)。
Follower 用于接收客戶請求并向客戶端返回結(jié)果,在選舉 Leader 過程中參與投票。
集群中只要有半數(shù)以上節(jié)點(diǎn)存活,Zookeeper 集群就能正常服務(wù)。
全局?jǐn)?shù)據(jù)一致(單一視圖):每個(gè) Server 保存一份相同的數(shù)據(jù)副本,Client 無論連接到哪個(gè) Server,數(shù)據(jù)都是一致的。
順序一致性: 從同一客戶端發(fā)起的事務(wù)請求,最終將會(huì)嚴(yán)格地按照順序被應(yīng)用到 ZooKeeper 中去。
原子性: 所有事務(wù)請求的處理結(jié)果在整個(gè)集群中所有機(jī)器上的應(yīng)用情況是一致的,也就是說,要么整個(gè)集群中所有的機(jī)器都成功應(yīng)用了某一個(gè)事務(wù),要么都沒有應(yīng)用。
實(shí)時(shí)性,在一定時(shí)間范圍內(nèi),client 能讀到最新數(shù)據(jù)。
可靠性: 一旦一次更改請求被應(yīng)用,更改的結(jié)果就會(huì)被持久化,直到被下一次更改覆蓋。
1.5 設(shè)計(jì)目標(biāo)
簡單的數(shù)據(jù)結(jié)構(gòu) :Zookeeper 使得分布式程序能夠通過一個(gè)共享的樹形結(jié)構(gòu)的名字空間來進(jìn)行相互協(xié)調(diào),即Zookeeper 服務(wù)器內(nèi)存中的數(shù)據(jù)模型由一系列被稱為ZNode的數(shù)據(jù)節(jié)點(diǎn)組成,Zookeeper 將全量的數(shù)據(jù)存儲(chǔ)在內(nèi)存中,以此來提高服務(wù)器吞吐、減少延遲的目的。
可以構(gòu)建集群 :Zookeeper 集群通常由一組機(jī)器構(gòu)成,組成 Zookeeper 集群的每臺(tái)機(jī)器都會(huì)在內(nèi)存中維護(hù)當(dāng)前服務(wù)器狀態(tài),并且每臺(tái)機(jī)器之間都相互通信。
順序訪問 :對于來自客戶端的每個(gè)更新請求,Zookeeper 都會(huì)分配一個(gè)全局唯一的遞增編號,這個(gè)編號反映了所有事務(wù)操作的先后順序。
高性能 :Zookeeper 和 Redis 一樣全量數(shù)據(jù)存儲(chǔ)在內(nèi)存中,100% 讀請求壓測 QPS 12-13W
1.6 數(shù)據(jù)結(jié)構(gòu)
Zookeeper 數(shù)據(jù)模型的結(jié)構(gòu)與 Unix 文件系統(tǒng)的結(jié)構(gòu)相似,整體上可以看做是一棵樹,每個(gè)節(jié)點(diǎn)稱作一個(gè) 「ZNode」。每個(gè) ZNode 默認(rèn)能存儲(chǔ) 1MB 的數(shù)據(jù),每個(gè) ZNode 都可以通過其路徑唯一標(biāo)識(shí)。
1.7 應(yīng)用場景
ZooKeeper 是一個(gè)典型的分布式數(shù)據(jù)一致性解決方案,分布式應(yīng)用程序可以基于 ZooKeeper 實(shí)現(xiàn)諸如數(shù)據(jù)發(fā)布/訂閱、負(fù)載均衡、命名服務(wù)、分布式協(xié)調(diào)/通知、集群管理、Master 選舉、分布式鎖和分布式隊(duì)列等功能
統(tǒng)一命名服務(wù)
在分布式系統(tǒng)中,通過使用命名服務(wù),客戶端應(yīng)用能夠根據(jù)指定名字來獲取資源或服務(wù)的地址,提供者等信息。被命名的實(shí)體通常可以是集群中的機(jī)器,提供的服務(wù)地址,進(jìn)程對象等等——這些我們都可以統(tǒng)稱他們?yōu)?strong>名字(Name)。其中較為常見的就是一些分布式服務(wù)框架(如RPC、RMI)中的服務(wù)地址列表。通過調(diào)用 Zookeeper 提供的創(chuàng)建節(jié)點(diǎn)的 API,能夠很容易創(chuàng)建一個(gè)全局唯一的 path,這個(gè) path 就可以作為一個(gè)名稱。
阿里巴巴開源的分布式服務(wù)框架 Dubbo 就使用 ZooKeeper 來作為其命名服務(wù),維護(hù)全局的服務(wù)地址列表。
數(shù)據(jù)發(fā)布與訂閱(配置中心)
發(fā)布與訂閱模型,即所謂的配置中心,顧名思義就是發(fā)布者將數(shù)據(jù)發(fā)布到 ZooKeeper 節(jié)點(diǎn)上,供訂閱者動(dòng)態(tài)獲取數(shù)據(jù),實(shí)現(xiàn)配置信息的集中式管理和動(dòng)態(tài)更新。例如全局的配置信息,服務(wù)式服務(wù)框架的服務(wù)地址列表等就非常適合使用。
分布式環(huán)境下,配置文件管理和同步是一個(gè)常見問題
一個(gè)集群中,所有節(jié)點(diǎn)的配置信息是一致的,比如 Hadoop 集群、集群中的數(shù)據(jù)庫配置信息等全局配置
對配置文件修改后,希望能夠快速同步到各個(gè)節(jié)點(diǎn)上。
配置管理可交由 ZooKeeper 實(shí)現(xiàn)
可將配置信息寫入 ZooKeeper 上的一個(gè) Znode
各個(gè)節(jié)點(diǎn)監(jiān)聽這個(gè) Znode
一旦 Znode 中的數(shù)據(jù)被修改,ZooKeeper 將通知各個(gè)節(jié)點(diǎn)
統(tǒng)一集群管理
所謂集群管理無在乎兩點(diǎn):是否有機(jī)器退出和加入、選舉 Master。
管理節(jié)點(diǎn)
分布式環(huán)境中,實(shí)時(shí)掌握每個(gè)節(jié)點(diǎn)的狀態(tài)是必要的,比如我們要知道集群中各機(jī)器狀態(tài)、收集各個(gè)機(jī)器的運(yùn)行時(shí)狀態(tài)數(shù)據(jù)、服務(wù)器動(dòng)態(tài)上下線等。
交由 ZooKeeper 實(shí)現(xiàn)的方式
可將節(jié)點(diǎn)信息寫入 ZooKeeper 上的一個(gè) Znode
監(jiān)聽這個(gè) Znode 可獲取它的實(shí)時(shí)狀態(tài)變化
典型應(yīng)用:HBase 中 Master 狀態(tài)監(jiān)控和選舉。(TODO:圖應(yīng)該是注冊和Register and watch)
心跳檢測中可以讓檢測系統(tǒng)和被檢測系統(tǒng)之間并不直接關(guān)聯(lián)起來,而是通過 ZK 上某個(gè)節(jié)點(diǎn)關(guān)聯(lián),減少系統(tǒng)耦合;
系統(tǒng)調(diào)度模式中,假設(shè)某系統(tǒng)有控制臺(tái)和推送系統(tǒng)兩部分組成,控制臺(tái)的職責(zé)是控制推送系統(tǒng)進(jìn)行相應(yīng)的推送工作。管理人員在控制臺(tái)作的一些操作,實(shí)際上是修改了 ZK 上某些節(jié)點(diǎn)的狀態(tài),而 ZK 就把這些變化通知給他們注冊 Watcher 的客戶端,即推送系統(tǒng),于是,作出相應(yīng)的推送任務(wù)。
所謂保持獨(dú)占,就是所有試圖來獲取這個(gè)鎖的客戶端,最終只有一個(gè)可以成功獲得這把鎖。通常的做法是把 zk 上的一個(gè) znode 看作是一把鎖,通過 create znode 的方式來實(shí)現(xiàn)。所有客戶端都去創(chuàng)建 /distribute_lock 節(jié)點(diǎn),最終成功創(chuàng)建的那個(gè)客戶端也即擁有了這把鎖。
控制時(shí)序,就是所有試圖來獲取這個(gè)鎖的客戶端,最終都是會(huì)被安排執(zhí)行,只是有個(gè)全局時(shí)序了。做法和上面基本類似,只是這里 /distribute_lock 已預(yù)先存在,客戶端在它下面創(chuàng)建臨時(shí)有序節(jié)點(diǎn)(這個(gè)可以通過節(jié)點(diǎn)的屬性控制:CreateMode.EPHEMERAL_SEQUENTIAL來指定)。ZK 的父節(jié)點(diǎn)(/distribute_lock)維持一份 sequence,保證子節(jié)點(diǎn)創(chuàng)建的時(shí)序性,從而也形成了每個(gè)客戶端的全局時(shí)序。
單機(jī)模式,即部署在單臺(tái)機(jī)器上的一個(gè) ZK 服務(wù),適用于學(xué)習(xí)、了解 ZK 基礎(chǔ)功能
偽分布模式,即部署在一臺(tái)機(jī)器上的多個(gè)(原則上大于3個(gè))ZK 服務(wù),偽集群,適用于學(xué)習(xí)、開發(fā)和測試
全分布式模式(復(fù)制模式),即在多臺(tái)機(jī)器上部署服務(wù),真正的集群模式,生產(chǎn)環(huán)境中使用
安裝 Jdk
拷貝或下載 Zookeeper 安裝包到 Linux 系統(tǒng)下(這里有個(gè)小問題,如果你下載 ZK 版本是3.5+ 的話,要下載 bin.tar.gz,愚笨的我最先沒看到官網(wǎng)說明,一頓操作各種報(bào)錯(cuò)找不到 Main 方法)
解壓到指定目錄
tar?-zxvf?apache-zookeeper-3.5.7-bin.tar.gz將 zookeeper-3.5.7/conf 這個(gè)路徑下的 zoo_sample.cfg 修改為 zoo.cfg ;
mv?zoo_sample.cfg?zoo.cfg打開 zoo.cfg 文件,修改 dataDir 路徑:
dataDir=XXX/zookeeper-3.5.7/zkData啟動(dòng) Zookeeper: ?bin/zkServer.sh start
/usr/local/bin/java
ZooKeeper?JMX?enabled?by?default
Using?config:?/home/sync360/test/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Starting?zookeeper?...?STARTED
查看進(jìn)程是否啟動(dòng): jps
4020?Jps
4001?QuorumPeerMain
查看狀態(tài):bin/zkServer.sh status
/usr/local/bin/java
ZooKeeper?JMX?enabled?by?default
Using?config:?/home/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client?port?found:?2181.?Client?address:?localhost.
Mode:?standalone
啟動(dòng)客戶端:bin/zkCli.sh
Connecting?to?localhost:2181
2020-03-25?15:41:19,112?[myid:]?-?INFO??[main:Environment@109]?-?Client?environment:zookeeper.version=3.5.7-f0fdd52973d373ffd9c86b81d99842dc2c7f660e,?built?on?02/10/2020?11:30?GMT...2020-03-25?15:41:19,183?[myid:]?-?INFO??[main:ClientCnxn@1653]?-?zookeeper.request.timeout?value?is?0.?feature?enabled=
Welcome?to?ZooKeeper!...WATCHER::WatchedEvent?state:SyncConnected?type:None?path:null
退出客戶端:quit
停止 Zookeeper: ?bin/zkServer.sh stop
tickTime =2000:通信心跳數(shù)
Zookeeper 使用的基本時(shí)間,服務(wù)器之間或客戶端與服務(wù)器之間維持心跳的時(shí)間間隔,也就是每個(gè) tickTime時(shí)間就會(huì)發(fā)送一個(gè)心跳,時(shí)間單位為毫秒
它用于心跳機(jī)制,并且設(shè)置最小的 session 超時(shí)時(shí)間為兩倍心跳時(shí)間。(session的最小超時(shí)時(shí)間是2*tickTime);
initLimit =10:主從初始通信時(shí)限,集群中的 Follower 跟隨者服務(wù)器與 Leader 領(lǐng)導(dǎo)者服務(wù)器之間初始連接時(shí)能容忍的最多心跳數(shù)(tickTime的數(shù)量),用它來限定集群中的 ZK 服務(wù)器連接到 Leader 的時(shí)限;
syncLimit =5:主從同步通信時(shí)限,集群中 Leader 與 Follower 之間的最大響應(yīng)時(shí)間單位,假如響應(yīng)超過syncLimit * tickTime,Leader 認(rèn)為 Follwer 死掉,從服務(wù)器列表中刪除 Follwer;
dataDir:數(shù)據(jù)文件目錄+數(shù)據(jù)持久化路徑;
clientPort =2181:客戶端連接端口
ZooKeeper 本身就是一個(gè)分布式程序(只要半數(shù)以上節(jié)點(diǎn)存活,ZooKeeper 就能正常服務(wù))。
為了保證高可用,最好是以集群形態(tài)來部署 ZooKeeper,這樣只要集群中大部分機(jī)器是可用的(能夠容忍一定的機(jī)器故障),那么 ZooKeeper 本身仍然是可用的。
ZooKeeper 將數(shù)據(jù)保存在內(nèi)存中,這也就保證了高吞吐量和低延遲(但是內(nèi)存限制了能夠存儲(chǔ)的容量不太大,此限制也是保持 znode 中存儲(chǔ)的數(shù)據(jù)量較小的進(jìn)一步原因)。
ZooKeeper 是高性能的。在“讀”多于“寫”的應(yīng)用程序中尤其的高性能,因?yàn)椤皩憽睍?huì)導(dǎo)致所有的服務(wù)器間同步狀態(tài)。(“讀”多于“寫”是協(xié)調(diào)服務(wù)的典型場景。)
ZooKeeper 底層其實(shí)只提供了兩個(gè)功能:
管理(存儲(chǔ)、讀取)用戶程序提交的數(shù)據(jù)
為用戶程序提交數(shù)據(jù)節(jié)點(diǎn)監(jiān)聽服務(wù)
所謂持久節(jié)點(diǎn)是指一旦這個(gè) ZNode 被創(chuàng)建了,除非主動(dòng)進(jìn)行 ZNode 的移除操作,否則這個(gè) ZNode 將一直保存在 Zookeeper 上。
而臨時(shí)節(jié)點(diǎn)就不一樣了,它的生命周期和客戶端會(huì)話綁定,一旦客戶端會(huì)話失效,那么這個(gè)客戶端創(chuàng)建的所有臨時(shí)節(jié)點(diǎn)都會(huì)被移除。
SessionID,用來全局唯一識(shí)別會(huì)話;
TimeOut,會(huì)話超時(shí)事件。客戶端在創(chuàng)造 Session 實(shí)例的時(shí)候,會(huì)設(shè)置一個(gè)會(huì)話超時(shí)的時(shí)間。當(dāng)由于服務(wù)器壓力太大、網(wǎng)絡(luò)故障或是客戶端主動(dòng)斷開連接等各種原因?qū)е驴蛻舳诉B接斷開時(shí),只要在 sessionTimeout 規(guī)定的時(shí)間內(nèi)能夠重新連接上集群中任意一臺(tái)服務(wù)器,那么之前創(chuàng)建的會(huì)話仍然有效;
TickTime,下次會(huì)話超時(shí)時(shí)間點(diǎn);
isClosing,當(dāng)服務(wù)端如果檢測到會(huì)話超時(shí)失效了,會(huì)通過設(shè)置這個(gè)屬性將會(huì)話關(guān)閉。
CREATE: 創(chuàng)建子節(jié)點(diǎn)的權(quán)限
READ: 獲取節(jié)點(diǎn)數(shù)據(jù)和子節(jié)點(diǎn)列表的權(quán)限
WRITE: 更新節(jié)點(diǎn)數(shù)據(jù)的權(quán)限
DELETE: 刪除子節(jié)點(diǎn)的權(quán)限
ADMIN: 設(shè)置節(jié)點(diǎn)ACL的權(quán)限
Leader:為客戶端提供讀和寫的服務(wù),負(fù)責(zé)投票的發(fā)起和決議,更新系統(tǒng)狀態(tài)
Follower:為客戶端提供讀服務(wù),如果是寫服務(wù)則轉(zhuǎn)發(fā)給 Leader。在選舉過程中參與投票
Observer:為客戶端提供讀服務(wù)器,如果是寫服務(wù)則轉(zhuǎn)發(fā)給 Leader。不參與選舉過程中的投票,也不參與“過半寫成功”策略。在不影響寫性能的情況下提升集群的讀性能。此角色是在 zookeeper3.3 系列新增的角色。
LOOKING:尋找Leader狀態(tài)
LEADING:領(lǐng)導(dǎo)者狀態(tài),表明當(dāng)前服務(wù)器角色是 Leader
FOLLOWING:跟隨者狀態(tài),表明當(dāng)前服務(wù)器角色是 Follower
OBSERVING:觀察者狀態(tài),表明當(dāng)前服務(wù)器角色是 Observer
服務(wù)器1啟動(dòng),此時(shí)只有它一臺(tái)服務(wù)器啟動(dòng)了,它發(fā)出去的報(bào)文沒有任何響應(yīng),所以它的選舉狀態(tài)一直是LOOKING 狀態(tài)。
服務(wù)器2啟動(dòng),它與最開始啟動(dòng)的服務(wù)器1進(jìn)行通信,互相交換自己的選舉結(jié)果,由于兩者都沒有歷史數(shù)據(jù),所以 id 值較大的服務(wù)器2勝出,但是由于沒有達(dá)到超過半數(shù)以上的服務(wù)器都同意選舉它(這個(gè)例子中的半數(shù)以上是3),所以服務(wù)器1、2還是繼續(xù)保持 LOOKING 狀態(tài)。
服務(wù)器3啟動(dòng),根據(jù)前面的理論分析,服務(wù)器3成為服務(wù)器1、2、3中的老大,而與上面不同的是,此時(shí)有三臺(tái)服務(wù)器選舉了它,所以它成為了這次選舉的Leader。
服務(wù)器4啟動(dòng),根據(jù)前面的分析,理論上服務(wù)器4應(yīng)該是服務(wù)器1、2、3、4中最大的,但是由于前面已經(jīng)有半數(shù)以上的服務(wù)器選舉了服務(wù)器3,所以它只能接受當(dāng)小弟的命了。
服務(wù)器5啟動(dòng),同4一樣當(dāng)小弟。
One-time trigger(一次性觸發(fā))
當(dāng)設(shè)置監(jiān)視的數(shù)據(jù)發(fā)生改變時(shí),該監(jiān)視事件會(huì)被發(fā)送到客戶端,例如,如果客戶端調(diào)用了 getData("/znode1", true) 并且稍后 /znode1 節(jié)點(diǎn)上的數(shù)據(jù)發(fā)生了改變或者被刪除了,客戶端將會(huì)獲取到 /znode1 發(fā)生變化的監(jiān)視事件,而如果 /znode1 再一次發(fā)生了變化,除非客戶端再次對 /znode1 設(shè)置監(jiān)視,否則客戶端不會(huì)收到事件通知。(3.6之后可以設(shè)置永久監(jiān)視)
Sent to the client(發(fā)送至客戶端)
Zookeeper 客戶端和服務(wù)端是通過 socket 進(jìn)行通信的,由于網(wǎng)絡(luò)存在故障,所以監(jiān)視事件很有可能不會(huì)成功到達(dá)客戶端,監(jiān)視事件是異步發(fā)送至監(jiān)視者的,Zookeeper 本身提供了保序性(ordering guarantee):即客戶端只有首先看到了監(jiān)視事件后,才會(huì)感知到它所設(shè)置監(jiān)視的 znode 發(fā)生了變化(a client will never see a change for which it has set a watch until it first sees the watch event)。網(wǎng)絡(luò)延遲或者其他因素可能導(dǎo)致不同的客戶端在不同的時(shí)刻感知某一監(jiān)視事件,但是不同的客戶端所看到的一切具有一致的順序。
The data for which the watch was set(被設(shè)置 watch 的數(shù)據(jù))
這意味著 znode 節(jié)點(diǎn)本身具有不同的改變方式。你也可以想象 Zookeeper 維護(hù)了兩條監(jiān)視鏈表:數(shù)據(jù)監(jiān)視和子節(jié)點(diǎn)監(jiān)視(data watches and child watches), getData() 和 exists() 設(shè)置數(shù)據(jù)監(jiān)視,getChildren() 設(shè)置子節(jié)點(diǎn)監(jiān)視。或者,你也可以想象 Zookeeper 設(shè)置的不同監(jiān)視返回不同的數(shù)據(jù),getData() 和 exists()返回 znode 節(jié)點(diǎn)的相關(guān)信息,而 getChildren() 返回子節(jié)點(diǎn)列表。因此, setData() 會(huì)觸發(fā)設(shè)置在某一節(jié)點(diǎn)上所設(shè)置的數(shù)據(jù)監(jiān)視(假定數(shù)據(jù)設(shè)置成功),而一次成功的 create() 操作則會(huì)觸發(fā)當(dāng)前節(jié)點(diǎn)上所設(shè)置的數(shù)據(jù)監(jiān)視以及父節(jié)點(diǎn)的子節(jié)點(diǎn)監(jiān)視。一次成功的 delete() 操作將會(huì)觸發(fā)當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)監(jiān)視和子節(jié)點(diǎn)監(jiān)視事件,同時(shí)也會(huì)觸發(fā)該節(jié)點(diǎn)父節(jié)點(diǎn)的 child watch。
客戶端向 ZooKeeper 服務(wù)器注冊一個(gè) Watcher 監(jiān)聽;
把這個(gè)監(jiān)聽信息存儲(chǔ)到客戶端的 WatchManager 中;
當(dāng) ZooKeeper 中的節(jié)點(diǎn)發(fā)生變化時(shí),會(huì)通知客戶端,客戶端會(huì)調(diào)用相應(yīng) Watcher 對象中的回調(diào)方法。
Master選舉
在分布式環(huán)境中,相同的業(yè)務(wù)應(yīng)用分布在不同的機(jī)器上,有些業(yè)務(wù)邏輯(例如一些耗時(shí)的計(jì)算,網(wǎng)絡(luò)I/O處理),往往只需要讓整個(gè)集群中的某一臺(tái)機(jī)器進(jìn)行執(zhí)行,其余機(jī)器可以共享這個(gè)結(jié)果,這樣可以大大減少重復(fù)勞動(dòng),提高性能,于是這個(gè)master選舉便是這種場景下的碰到的主要問題。
利用 Zookeeper 的強(qiáng)一致性,能夠很好的保證在分布式高并發(fā)情況下節(jié)點(diǎn)的創(chuàng)建一定是全局唯一的,即:同時(shí)有多個(gè)客戶端請求創(chuàng)建 /currentMaster 節(jié)點(diǎn),最終一定只有一個(gè)客戶端請求能夠創(chuàng)建成功。Zookeeper 通過這種節(jié)點(diǎn)唯一的特性,可以創(chuàng)建一個(gè) Master 節(jié)點(diǎn),其他客戶端 Watcher 監(jiān)控當(dāng)前 Master 是否存活,一旦 Master 掛了,其他機(jī)器再創(chuàng)建這樣的一個(gè) Master 節(jié)點(diǎn),用來重新選舉。
軟負(fù)載均衡
分布式系統(tǒng)中,負(fù)載均衡是一種很普遍的技術(shù),為了保證高可用性,通常同一個(gè)應(yīng)用或同一個(gè)服務(wù)的提供方都會(huì)部署多份,達(dá)到對等服務(wù)。可以是硬件的負(fù)載均衡,如 F5,也可以是軟件的負(fù)載,我們熟知的 Nginx,或者這里介紹的 Zookeeper。
分布式協(xié)調(diào)/通知
Zookeeper 中特有的 「Watcher」 注冊與異步通知機(jī)制,能夠很好的實(shí)現(xiàn)分布式環(huán)境下不同機(jī)器,甚至不同系統(tǒng)之間的協(xié)調(diào)和通知,從而實(shí)現(xiàn)對數(shù)據(jù)變更的實(shí)時(shí)處理。
使用方法通常是不同系統(tǒng)都對 ZK 上同一個(gè) znode 進(jìn)行注冊,監(jiān)聽 znode 的變化(包括 znode 本身內(nèi)容及子節(jié)點(diǎn)的),其中一個(gè)系統(tǒng) update 了 znode,那么另一個(gè)系統(tǒng)能夠收到通知,并作出相應(yīng)處理。
分布式鎖
分布式鎖,這個(gè)主要得益于 ZooKeeper 為我們保證了數(shù)據(jù)的強(qiáng)一致性。
鎖服務(wù)可以分為兩類,一個(gè)是保持獨(dú)占,另一個(gè)是控制時(shí)序。
個(gè)人感覺還是用 Redis 實(shí)現(xiàn)分布式鎖更加方便。
PS:阿里中間件團(tuán)隊(duì):“其實(shí),ZK 并非天生就是為這些應(yīng)用場景設(shè)計(jì)的,都是后來眾多開發(fā)者根據(jù)其框架的特性,利用其提供的一系列API接口(或者稱為原語集),摸索出來的典型使用方法。”
2. Hello ZooKeeper
ZooKeeper 的三種部署方式:
計(jì)劃寫三篇的,第二篇會(huì)實(shí)戰(zhàn) coding,運(yùn)用各種 API,到時(shí)候再裝集群,本節(jié)先來個(gè)單機(jī)玩~~
2.1 本地模式安裝部署
2.1.1 安裝前準(zhǔn)備
2.1.2 配置修改
2.1.3 操作 Zookeeper
2.2 常用命令
| help | 顯示所有操作命令 |
| ls path [watch] | 使用 ls 命令來查看當(dāng)前znode中所包含的內(nèi)容 |
| ls2 path [watch] | 查看當(dāng)前節(jié)點(diǎn)數(shù)據(jù)并能看到更新次數(shù)等數(shù)據(jù) |
| create | 普通創(chuàng)建-s ?含有序列-e ?臨時(shí)(重啟或者超時(shí)消失) |
| get path [watch] | 獲得節(jié)點(diǎn)的值 |
| set | 設(shè)置節(jié)點(diǎn)的具體值 |
| stat | 查看節(jié)點(diǎn)狀態(tài) |
| delete | 刪除節(jié)點(diǎn) |
| rmr | 遞歸刪除節(jié)點(diǎn) |
ls 查看當(dāng)前 zk 中所包含的內(nèi)容
[zk:?localhost:2181(CONNECTED)?1]?ls?/ [lazyegg,?zookeeper]create 創(chuàng)建一個(gè)新的 znode
[zk:?localhost:2181(CONNECTED)?2]?create?/test Created?/testget 查看新的 znode 的值
[zk:?localhost:2181(CONNECTED)?4]?get?/test null可以看到值為 null,我們剛才設(shè)置了一個(gè)沒有值的節(jié)點(diǎn),也可以通過 create /zoo dog 直接創(chuàng)建有內(nèi)容的節(jié)點(diǎn)
set 對 zk 所關(guān)聯(lián)的字符串進(jìn)行設(shè)置
set?/test?hellodelete 刪除節(jié)點(diǎn)
delete?/test2.3 配置參數(shù)解讀
在 Zookeeper 的設(shè)計(jì)中,如果是集群模式,那所有機(jī)器上的 zoo.cfg 文件內(nèi)容應(yīng)該都是一致的。
Zookeeper 中的配置文件 zoo.cfg 中參數(shù)含義解讀如下:
?
3. 你要知道的概念
這里引入一個(gè)簡單的例子,逐個(gè)介紹一些 ZK 中的概念。
在分布式系統(tǒng)中經(jīng)常會(huì)遇到這種情況,多個(gè)應(yīng)用讀取同一個(gè)配置。例如:Client1,Client2 兩個(gè)應(yīng)用都會(huì)讀取配置 B 中的內(nèi)容,一旦 B 中的內(nèi)容出現(xiàn)變化,就會(huì)通知 Client1 和 Client2。
一般的做法是在 Client1,Client2 中按照時(shí)鐘頻率詢問 B 的變化,或者使用觀察者模式來監(jiān)聽 B 的變化,發(fā)現(xiàn)變化以后再更新兩個(gè)客戶端。那么 ZooKeeper 如何協(xié)調(diào)這種場景?
這兩個(gè)客戶端連接到 ZooKeeper 的服務(wù)器,并獲取其中存放的 B。保存 B 值的地方在 ZooKeeper 服務(wù)端中就稱為 ZNode。
3.1 數(shù)據(jù)節(jié)點(diǎn)(Znode)
在談到分布式的時(shí)候,我們通常說的“節(jié)點(diǎn)"是指組成集群的每一臺(tái)機(jī)器。然而,在 Zookeeper 中,“節(jié)點(diǎn)"分為兩類,第一類同樣是指構(gòu)成集群的機(jī)器,我們稱之為「機(jī)器節(jié)點(diǎn)」;第二類則是指數(shù)據(jù)模型中的數(shù)據(jù)單元,我們稱之為「數(shù)據(jù)節(jié)點(diǎn)」一一ZNode。上圖中的 A、B 就是一個(gè)數(shù)據(jù)結(jié)點(diǎn)。
Zookeeper 將所有數(shù)據(jù)存儲(chǔ)在內(nèi)存中,數(shù)據(jù)模型是一棵樹(Znode Tree),由斜杠(/)進(jìn)行分割的路徑,就是一個(gè) Znode,例如 /Configuration/B。每個(gè) Znode 上都會(huì)保存自己的數(shù)據(jù)內(nèi)容,同時(shí)還會(huì)保存一系列屬性信息。
在 Zookeeper 中,Znode 可以分為持久節(jié)點(diǎn)和臨時(shí)節(jié)點(diǎn)兩類。
另外,ZooKeeper 還允許用戶為每個(gè)節(jié)點(diǎn)添加一個(gè)特殊的屬性:**SEQUENTIAL。**也被叫做 順序結(jié)點(diǎn),一旦節(jié)點(diǎn)被標(biāo)記上這個(gè)屬性,那么在這個(gè)節(jié)點(diǎn)被創(chuàng)建的時(shí)候,Zookeeper 會(huì)自動(dòng)在其節(jié)點(diǎn)名后面追加上一個(gè)整型數(shù)字,這個(gè)整型數(shù)字是一個(gè)由父節(jié)點(diǎn)維護(hù)的自增數(shù)字。
3.2 事件監(jiān)聽器(Watcher)
上面說了 ZooKeeper 用來存放數(shù)據(jù)的 ZNode,并且把 B 的值存儲(chǔ)在里面。如果 B 被更新了,兩個(gè)客戶端(Client1、Client2)如何獲得通知呢?
Zookeeper 允許用戶在指定節(jié)點(diǎn)上注冊一些 Watcher,當(dāng) Znode 發(fā)生變化時(shí),將觸發(fā)并刪除一個(gè) watch。當(dāng) watch 被觸發(fā)時(shí)客戶端會(huì)收到一個(gè)數(shù)據(jù)包,指示 znode 已經(jīng)被修改。如果客戶端和 ZooKeeper 服務(wù)器之間的連接中斷,客戶端將收到本地通知。該機(jī)制是 Zookeeper 實(shí)現(xiàn)分布式協(xié)調(diào)服務(wù)的重要特性。
3.6.0中的新增功能:客戶端還可以在 znode 上設(shè)置永久性的遞歸監(jiān)視,這些監(jiān)視在觸發(fā)時(shí)不會(huì)刪除,并且會(huì)以遞歸方式觸發(fā)已注冊 znode 以及所有子 znode 的更改。
ZooKeeper 客戶端(Client)會(huì)在指定的節(jié)點(diǎn)(/Configuration/B)上注冊一個(gè) Watcher,ZNode 上的 B 被更新的時(shí)候,服務(wù)端就會(huì)通知 Client1 和 Client2。
3.3 版本
有了 Watcher 機(jī)制,就可以實(shí)現(xiàn)分布式協(xié)調(diào)/通知了,假設(shè)有這樣的場景,兩個(gè)客戶端同時(shí)對 B 進(jìn)行寫入操作,這兩個(gè)客戶端就會(huì)存在競爭關(guān)系,通常需要對 B 進(jìn)行加鎖操作,ZK 通過 version 版本號來控制實(shí)現(xiàn)樂觀鎖中的“寫入校驗(yàn)”機(jī)制。
Zookeeper 的每個(gè) ZNode 上都會(huì)存儲(chǔ)數(shù)據(jù),對應(yīng)于每個(gè) ZNode,Zookeeper 都會(huì)為其維護(hù)一個(gè)叫作 Stat 的數(shù)據(jù)結(jié)構(gòu),Stat 中記錄了這個(gè) ZNode 的三個(gè)數(shù)據(jù)版本,分別是 version(當(dāng)前ZNode的版本)、cversion(當(dāng)前ZNode 子節(jié)點(diǎn)的版本)和 aversion(當(dāng)前ZNode的ACL版本)。
znode 里都有些啥呢?
3.4 Stat 結(jié)構(gòu)體
Znodes 維護(hù)了一個(gè) stat 結(jié)構(gòu),其中包含數(shù)據(jù)更改、ACL更改的版本號、時(shí)間戳等。
| czxid | 創(chuàng)建節(jié)點(diǎn)的事務(wù)zxid。每次修改 ZK 狀態(tài)都會(huì)收到一個(gè)zxid形式的時(shí)間戳,也就是 ZK 事務(wù)ID。事務(wù)ID是 ZK 中所有修改總的次序。每個(gè)修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前發(fā)生 |
| ctime | znode被創(chuàng)建的毫秒數(shù)(從1970年開始) |
| mzxid | znode最后更新的事務(wù)zxid |
| mtime | znode最后修改的毫秒數(shù)(從1970年開始) |
| pzxid | znode最后更新的子節(jié)點(diǎn)zxid |
| version | 數(shù)據(jù)節(jié)點(diǎn)版本號 |
| cversion | 子節(jié)點(diǎn)版本號,znode子節(jié)點(diǎn)修改次數(shù) |
| aversion | znode訪問控制列表的變化號 |
| ephemeralOwner | 如果是臨時(shí)節(jié)點(diǎn),這個(gè)是znode擁有者的session id。如果不是臨時(shí)節(jié)點(diǎn)則是0 |
| dataLength | znode的數(shù)據(jù)長度 |
| numChildren | znode子節(jié)點(diǎn)數(shù)量 |
3.5 會(huì)話(Session)
Session 指的是 ZooKeeper 服務(wù)器與客戶端會(huì)話。
在 ZooKeeper 中,一個(gè)客戶端連接是指客戶端和服務(wù)器之間的一個(gè) TCP 長連接。客戶端啟動(dòng)的時(shí)候,首先會(huì)與服務(wù)器建立一個(gè) TCP 連接,從第一次連接建立開始,客戶端會(huì)話的生命周期也開始了。通過這個(gè)連接,客戶端能夠通過心跳檢測與服務(wù)器保持有效的會(huì)話,也能夠向 Zookeeper 服務(wù)器發(fā)送請求并接受響應(yīng),同時(shí)還能夠通過該連接接收來自服務(wù)器的 Watch 事件通知。
Session 作為會(huì)話實(shí)體,用來代表客戶端會(huì)話,其包括 4 個(gè)屬性:
3.6 ACL
Zookeeper 采用 ACL(Access Control Lists)策略來進(jìn)行權(quán)限控制,類似于 UNIX 文件系統(tǒng)的權(quán)限控制。Zookeeper 定義了如下 5 種權(quán)限:
其中尤其需要注意的是,CREATE 和 DELETE 這兩種權(quán)限都是針對子節(jié)點(diǎn)的權(quán)限控制。
3.7 集群角色
最典型集群模式:Master/Slave 模式(主備模式)。在這種模式中,通常 Master 服務(wù)器作為主服務(wù)器提供寫服務(wù),其他的 Slave 從服務(wù)器通過異步復(fù)制的方式獲取 Master 服務(wù)器最新的數(shù)據(jù)提供讀服務(wù)。
但是,在 ZooKeeper 中沒有選擇傳統(tǒng)的 Master/Slave 概念,而是引入了Leader、Follower 和 Observer 三種角色。
server 狀態(tài)
選舉機(jī)制
zk-vote
Watcher 監(jiān)聽器
Zookeeper 中最有特色且最不容易理解的是監(jiān)視(Watches)。
Zookeeper 所有的讀操作——getData(),getChildren(), 和 exists() 都可以設(shè)置監(jiān)視(watch),監(jiān)視事件可以理解為一次性的觸發(fā)器, 官方定義如下:a watch event is one-time trigger, sent to the client that set the watch, which occurs when the data for which the watch was set changes。對此需要作出如下理解:
Zookeeper 中的監(jiān)視是輕量級的,因此容易設(shè)置、維護(hù)和分發(fā)。當(dāng)客戶端與 Zookeeper 服務(wù)器端失去聯(lián)系時(shí),客戶端并不會(huì)收到監(jiān)視事件的通知,只有當(dāng)客戶端重新連接后,若在必要的情況下,以前注冊的監(jiān)視會(huì)重新被注冊并觸發(fā),對于開發(fā)人員來說這通常是透明的。只有一種情況會(huì)導(dǎo)致監(jiān)視事件的丟失,即:通過 exists() 設(shè)置了某個(gè) znode 節(jié)點(diǎn)的監(jiān)視,但是如果某個(gè)客戶端在此 znode 節(jié)點(diǎn)被創(chuàng)建和刪除的時(shí)間間隔內(nèi)與 zookeeper 服務(wù)器失去了聯(lián)系,該客戶端即使稍后重新連接 zookeepe r服務(wù)器后也得不到事件通知。
圖片來源:yht7
從上圖可以看到,Watcher 機(jī)制包括三個(gè)角色:客戶端線程、客戶端的 WatchManager 以及 ZooKeeper 服務(wù)器。Watcher 機(jī)制就是這三個(gè)角色之間的交互,整個(gè)過程分為注冊、存儲(chǔ)和通知三個(gè)步驟:
參考:
《從Paxos到ZooKeeper 分布式一致性原理與實(shí)踐》
《阿里中間件團(tuán)隊(duì)博客》http://jm.taobao.org/2011/10/08/1232/
《Zookeeper官方文檔》https://zookeeper.apache.org/doc/
《尚硅谷Zookeeper》
https://cloud.tencent.com/developer/article/1578401
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號
好文章,我在看??
總結(jié)
以上是生活随笔為你收集整理的不懂 ZooKeeper?没关系,这一篇给你讲的明明白白的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关键字之defer、panic、reco
- 下一篇: Golang的匿名函数和闭包