Zookeeper簡介:
Zookeeper是什麼:
Zookeeper 是⼀個分散式協調服務的開源框架。 主要⽤來解決分散式叢集中應⽤系統的⼀致性問題, 例如怎樣避免同時操作同⼀資料造成髒讀的問題。分散式系統中資料存在⼀致性的問題!!
- ZooKeeper 本質上是⼀個分散式的⼩⽂件儲存系統。 提供基於類似於⽂件系統的⽬錄樹⽅式的數 據儲存,並且可以對樹中的節點進⾏有效管理。
- ZooKeeper 提供給客戶端監控儲存在zk內部資料的功能,從⽽可以達到基於資料的叢集管理。 諸 如: 統⼀命名服務(dubbo)、分散式配置管理(solr的配置集中管理)、分散式訊息佇列 (sub/pub)、分散式鎖、分散式協調等功能。
架構組成:
Leader
- Zookeeper 叢集⼯作的核⼼⻆⾊
- 叢集內部各個伺服器的排程者。
- 事務請求(寫操作) 的唯⼀排程和處理者,保證叢集事務處理的順序性;對於 create, setData, delete 等有寫操作的請求,則需要統⼀轉發給leader 處理, leader 需要決定編號、執 ⾏操作,這個過程稱為⼀個事務。
Follower
- 處理客戶端⾮事務(讀操作) 請求,
- 轉發事務請求給 Leader;
- 參與叢集 Leader 選舉投票 2n-1臺可以做叢集投票。
此外,針對訪問量⽐較⼤的 zookeeper 叢集, 還可新增觀察者⻆⾊。
Observer
- 觀察者⻆⾊,觀察 Zookeeper 叢集的最新狀態變化並將這些狀態同步過來,其對於⾮事務請求可 以進⾏獨⽴處理,對於事務請求,則會轉發給 Leader伺服器進⾏處理。
- 不會參與任何形式的投票只提供⾮事務服務,通常⽤於在不影響叢集事務處理能⼒的前提下提升集 群的⾮事務處理能⼒。增加了叢集增加併發的讀請求
ZK也是Master/slave架構,但是與之前不同的是zk叢集中的Leader不是指定⽽來,⽽是通過選舉產⽣。
Zookeeper 特點:
- 1.Zookeeper:⼀個領導者(leader:⽼⼤),多個跟隨者(follower:⼩弟)組成的叢集。
- 2. Leader負責進⾏投票的發起和決議,更新系統狀態(內部原理)
- 3. Follower⽤於接收客戶請求並向客戶端返回結果,在選舉Leader過程中參與投票
- 4. 叢集中只要有半數以上節點存活,Zookeeper叢集就能正常服務。
- 5. 全域性資料⼀致:每個server儲存⼀份相同的資料副本,Client⽆論連線到哪個server,資料都是⼀ 致的。
- 6. 更新請求順序進⾏(內部原理)
- 7. 資料更新原⼦性,⼀次資料更新要麼成功,要麼失敗
Zookeeper 節點 之 ZNode 型別:
Zookeeper 節點型別可以分為三⼤類:
- 永續性節點(Persistent)
- 臨時性節點(Ephemeral)
- 順序性節點(Sequential)
在開發中在建立節點的時候通過組合可以⽣成以下四種節點型別:持久節點、持久順序節點、臨時節 點、臨時順序節點。不同型別的節點則會有不同的⽣命週期
持久節點:是Zookeeper中最常⻅的⼀種節點型別,所謂持久節點,就是指節點被建立後會⼀直存在服 務器,直到刪除操作主動清除
持久順序節點:就是有順序的持久節點,節點特性和持久節點是⼀樣的,只是額外特性表現在順序上。 順序特性實質是在建立節點的時候,會在節點名後⾯加上⼀個數字字尾,來表示其順序。
臨時節點:就是會被⾃動清理掉的節點,它的⽣命週期和客戶端會話綁在⼀起,客戶端會話結束,節點 會被刪除掉。與永續性節點不同的是,臨時節點不能建立⼦節點。
臨時順序節點:就是有順序的臨時節點,和持久順序節點相同,在其建立的時候會在名字後⾯加上數字 字尾
事務ID:
在ZooKeeper中,事務是指能夠改變ZooKeeper伺服器狀態的操作,我們也稱之為事務操作或更新 操作,⼀般包括資料節點建立與刪除、資料節點內容更新等操作。對於每⼀個事務請求,ZooKeeper都 會為其分配⼀個全域性唯⼀的事務ID,⽤ ZXID 來表示,通常是⼀個 64 位的數字。每⼀個 ZXID 對應⼀次 更新操作,從這些ZXID中可以間接地識別出ZooKeeper處理這些更新操作請求的全域性順序 zk中的事務指的是對zk伺服器狀態改變的操作(create,update data,更新位元組點);zk對這些事務操作都 會編號,這個編號是⾃增⻓的被稱為ZXID。
ZNode 的狀態資訊:
#使⽤bin/zkCli.sh 連線到zk叢集 [zk: localhost:2181(CONNECTED) 2] get /zookeeper cZxid = 0x0 ctime = Wed Dec 31 19:00:00 EST 1969 mZxid = 0x0 mtime = Wed Dec 31 19:00:00 EST 1969 pZxid = 0x0 cversion = -1 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 0 numChildren = 1
含義:
cZxid 就是 Create ZXID,表示節點被建立時的事務ID。 ctime 就是 Create Time,表示節點建立時間。 mZxid 就是 Modified ZXID,表示節點最後⼀次被修改時的事務ID。 mtime 就是 Modified Time,表示節點最後⼀次被修改的時間。 pZxid 表示該節點的⼦節點列表最後⼀次被修改時的事務 ID。只有⼦節點列表變更才會更新 pZxid, ⼦節點內容變更不會更新。 cversion 表示⼦節點的版本號。 dataVersion 表示內容版本號。 aclVersion 標識acl版本 ephemeralOwner 表示建立該臨時節點時的會話 sessionID,如果是永續性節點那麼值為 0 dataLength 表示資料⻓度。 numChildren 表示直系⼦節點數。
Watcher 機制:
在 ZooKeeper 中,引⼊了 Watcher 機制來實現這種分散式的通知功能。ZooKeeper 允許客戶端向服務 端註冊⼀個 Watcher 監聽,當服務端的⼀些指定事件觸發了這個 Watcher,那麼Zk就會向指定客戶端 傳送⼀個事件通知來實現分散式的通知功能。
Zookeeper的Watcher機制主要包括客戶端執行緒、客戶端WatcherManager、Zookeeper伺服器三部 分。
具體⼯作流程為:
- 客戶端在向Zookeeper伺服器註冊的同時,會將Watcher物件儲存在客戶端的WatcherManager當 中
- 當Zookeeper伺服器觸發Watcher事件後,會向客戶端傳送通知
- 客戶端執行緒從WatcherManager中取出對應的Watcher物件來執⾏回撥邏輯
注: 客戶端負責watch的註冊 和回撥,zk伺服器負責處理watch。
命令列使用:
建立順序節點:create -s /zk-test 123 建立臨時節點:create -e /zk-temp 123 建立永久節點:create /zk-permanent 123 讀取節點:ls path 其中,path表示的是指定資料節點的節點路徑 獲取內容:get path 更新節點:set path data 刪除節點: delete path
JAVA 操作Zookeeper:
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.14</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.2</version> </dependency>
建立會話:
package com.hust.grid.leesf.zkclient.examples; import java.io.IOException; import org.I0Itec.zkclient.ZkClient; public class CreateSession { /* 建立⼀個zkClient例項來進⾏連線 */ public static void main(String[] args) { ZkClient zkClient = new ZkClient("127.0.0.1:2181"); System.out.println("ZooKeeper session created."); } }
建立節點:
package com.hust.grid.leesf.zkclient.examples; import org.I0Itec.zkclient.ZkClient; public class Create_Node_Sample { public static void main(String[] args) { ZkClient zkClient = new ZkClient("127.0.0.1:2181"); System.out.println("ZooKeeper session established."); //createParents的值設定為true,可以遞迴建立節點 zkClient.createPersistent("/lg-zkClient/lg-c1",true); System.out.println("success create znode."); } }
刪除節點:
package com.hust.grid.leesf.zkclient.examples; import org.I0Itec.zkclient.ZkClient; public class Del_Data_Sample { public static void main(String[] args) throws Exception { String path = "/lg-zkClient/lg-c1"; ZkClient zkClient = new ZkClient("127.0.0.1:2181", 5000); zkClient.deleteRecursive(path); System.out.println("success delete znode."); } }
監聽節點變化:
import org.I0Itec.zkclient.IZkChildListener; import org.I0Itec.zkclient.ZkClient; import org.apache.zookeeper.client.ZooKeeperSaslClient; import java.util.List; /* 演示zkClient如何使⽤監聽器 */ public class Get_Child_Change { public static void main(String[] args) throws InterruptedException { //獲取到zkClient final ZkClient zkClient = new ZkClient("linux121:2181"); //zkClient對指定⽬錄進⾏監聽(不存在⽬錄:/lg-client),指定收到通知之後的邏輯 //對/lag-client註冊了監聽器,監聽器是⼀直監聽 zkClient.subscribeChildChanges("/lg-client", new IZkChildListener() { //該⽅法是接收到通知之後的執⾏邏輯定義 public void handleChildChange(String path, List<String> childs) throws Exception { //列印節點資訊 System.out.println(path + " childs changes ,current childs " + childs); } }); //使⽤zkClient建立節點,刪除節點,驗證監聽器是否運⾏ zkClient.createPersistent("/lg-client"); Thread.sleep(1000); //只是為了⽅便觀察結果資料 zkClient.createPersistent("/lg-client/c1"); Thread.sleep(1000); zkClient.delete("/lg-client/c1"); Thread.sleep(1000); zkClient.delete("/lg-client"); Thread.sleep(Integer.MAX_VALUE); /* 1 監聽器可以對不存在的⽬錄進⾏監聽 2 監聽⽬錄下⼦節點發⽣改變,可以接收到通知,攜帶資料有⼦節點列表 3 監聽⽬錄建立和刪除本身也會被監聽到 */ } }
執行結果:
/lg-zkClient 's child changed, currentChilds:[] /lg-zkClient 's child changed, currentChilds:[c1] /lg-zkClient 's child changed, currentChilds:[] /lg-zkClient 's child changed, currentChilds:null
注:
- 客戶端可以對⼀個不存在的節點進⾏⼦節點變更的監聽。
- ⼀旦客戶端對⼀個節點註冊了⼦節點列表變更監聽之後,那麼當該節點的⼦節點列表發⽣變更時,服務 端都會通知客戶端,並將最新的⼦節點列表傳送給客戶端
- 該節點本身的建立或刪除也會通知到客戶端。
監聽節點資料變化:
import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.ZkClient; //使⽤監聽器監聽節點資料的變化 public class Get_Data_Change { public static void main(String[] args) throws InterruptedException { // 獲取zkClient物件 final ZkClient zkClient = new ZkClient("linux121:2181"); //設定⾃定義的序列化型別,否則會報錯!! zkClient.setZkSerializer(new ZkStrSerializer()); //判斷節點是否存在,不存在建立節點並賦值 final boolean exists = zkClient.exists("/lg-client1"); if (!exists) { zkClient.createEphemeral("/lg-client1", "123"); } //註冊監聽器,節點資料改變的型別,接收通知後的處理邏輯定義 zkClient.subscribeDataChanges("/lg-client1", new IZkDataListener() { public void handleDataChange(String path, Object data) throws Exception { //定義接收通知之後的處理邏輯 System.out.println(path + " data is changed ,new data " + data); } //資料刪除--》節點刪除 public void handleDataDeleted(String path) throws Exception { System.out.println(path + " is deleted!!"); } }); //更新節點的資料,刪除節點,驗證監聽器是否正常運⾏ final Object o = zkClient.readData("/lg-client1"); System.out.println(o); zkClient.writeData("/lg-client1", "new data"); Thread.sleep(1000); //刪除節點 zkClient.delete("/lg-client1"); Thread.sleep(Integer.MAX_VALUE); } }
zk 自定義字串序列化:
import org.I0Itec.zkclient.exception.ZkMarshallingError; import org.I0Itec.zkclient.serialize.ZkSerializer; public class ZkStrSerializer implements ZkSerializer { //序列化,資料--》byte[] public byte[] serialize(Object o) throws ZkMarshallingError { return String.valueOf(o).getBytes(); } //反序列化,byte[]--->資料 public Object deserialize(byte[] bytes) throws ZkMarshallingError { return new String(bytes); } }
Leader選舉:
選舉機制:
- 半數機制:叢集中半數以上機器存活,叢集可⽤。所以Zookeeper適合安裝奇數臺伺服器。
- Zookeeper雖然在配置⽂件中並沒有指定Master和Slave。但是,Zookeeper⼯作時,是有⼀個節 點為Leader,其它為Follower,Leader是通過內部的選舉機制產⽣的。
- 只有當機器減少或叢集初次啟動才會選舉leader。
詳細步驟:
- (1)伺服器1啟動,此時只有它⼀臺伺服器啟動了,它發出去的報⽂沒有任何響應,所以它的選舉狀態 ⼀直是LOOKING狀態。
- (2)伺服器2啟動,它與最開始啟動的伺服器1進⾏通訊,互相交換⾃⼰的選舉結果,由於兩者都沒有 歷史資料,所以id值較⼤的伺服器2勝出,但是由於沒有達到超過半數以上的伺服器都同意選舉它(這個 例⼦中的半數以上是3),所以伺服器1、2還是繼續保持LOOKING狀態。 return String.valueOf(o).getBytes(); } //反序列化,byte[]--->資料 public Object deserialize(byte[] bytes) throws ZkMarshallingError { return new String(bytes); } } 123 /lg-client1 data is changed ,new data new data /lg-client1 is deleted!!
- (3)伺服器3啟動,根據前⾯的理論分析,伺服器3成為伺服器1、2、3中的⽼⼤,⽽與上⾯不同的 是,此時有三臺伺服器選舉了它,所以它成為了這次選舉的Leader。
- (4)伺服器4啟動,根據前⾯的分析,理論上伺服器4應該是伺服器1、2、3、4中最⼤的,但是由於前 ⾯已經有半數以上的伺服器選舉了伺服器3,所以它只能接收當⼩弟的命了。
- (5)伺服器5啟動,同4⼀樣稱為follower
叢集首次啟動:
半數前選myid 最大的機器。
非首次啟動:
優先選擇zxid值⼤的節點稱為Leader!!
ZAB⼀致性協議:
ZAB 協議是為分散式協調服務 Zookeeper 專⻔設計的⼀種⽀持崩潰恢復和原⼦⼴播協議
原子廣播:
具體流程:
總結: 第一步傳送提議,如果提議獲得半數以上機器的ack,然後傳送commit給follower,同時自己commit。
崩潰恢復:
Leader當機後,被選舉的新Leader需要解決的問題:
- ZAB 協議確保那些已經在 Leader 提交的事務最終會被所有伺服器提交。
- ZAB 協議確保丟棄那些只在 Leader 提出/複製,但沒有提交的事務。
選舉演算法的關鍵點:保證選舉出的新Leader擁有叢集中所有節點最⼤編號(ZXID)的事務!!
總結:leader崩潰,新的leader必須擁有最大的事務id,這樣才能保證資料最新。