0. 前言
文章已經收錄到 GitHub 個人部落格專案,歡迎 Star:
https://github.com/chenyl8848/chenyl8848.github.io
或者訪問網站,進行線上瀏覽:
https://chenyl8848.github.io/
1. ZooKeeper 簡介
ZooKeeper(動物園管理者)簡稱 ZK,一個分散式的,開放原始碼的分散式應用程式協調服務,是 Google 的 Chubby 一個開源的實現,是 Hadoop 和 Hbase 的重要元件。ZooKeeper 使用 Java 所編寫,但是支援 Java 和 C 兩種程式語言。
應用場景:
- 分散式微服務註冊中心:Dubbo 框架、Spring Cloud 框架
- 叢集管理:Hadoop Hbase 元件
- 分散式鎖
關注微信公眾號:【Java陳序員】,獲取開源專案分享、AI副業分享、超200本經典計算機電子書籍等。
2. ZooKeeper 記憶體資料模型
2.1 模型結構
2.2 模型的特點
- 每個子目錄如
/node1
都被稱作一個znode
(節點),這個znode
是被它所在的路徑唯一標識 znode
可以有子節點目錄,並且每個znode
可以儲存資料znode
是有版本的,每個znode
中儲存的資料可以有多個版本,也就是一個訪問路徑中可以儲存多份資料znode
可以被監控,包括這個目錄節點中儲存的資料的修改,子節點目錄的變化等,一旦變化可以通知設定監控的客戶端
3. 節點的分類
3.1 持久節點(PERSISTENT)
指在節點建立後,就一直存在,直到有刪除操作來主動刪除這個節點 —— 不會因為建立該節點的客戶端會話失效而消失。
3.2 持久順序節點(PERSISTENT_SEQUENTIAL)
這類節點的基本特性和上面的節點型別是一致的。額外的特性是,在 ZooKeeper 中,每個父節點會為他的第一級子節點維護一份時序,會記錄每個子節點建立的先後順序。基於這個特性,在建立子節點的時候,可以設定這個屬性,那麼在建立節點過程中,ZooKeeper 會自動為給定節點名加上一個數字字尾,作為新的節點名。這個數字字尾的範圍是整型的最大值。
3.3 臨時節點(EPHEMERAL)
和持久節點不同的是,臨時節點的生命週期和客戶端會話繫結。也就是說,如果客戶端會話失效,那麼這個節點就會自動被清除掉。注意,這裡提到的是會話失效,而非連線斷開。另外,在臨時節點下面不能建立子節點。
3.4 臨時順序節點(EPHEMERAL_SEQUENTIAL)
具有臨時節點特點,額外的特性是,每個父節點會為他的第一級子節點維護一份時序,這點和持久順序節點類似。
4. 安裝
4.1 Linux 系統安裝
# 1.安裝 JDK 並配置環境變數&下載 ZooKeeper 安裝包
- https://mirrors.bfsu.edu.cn/apache/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz
# 2.下載安裝包上傳到 Linux 伺服器中,並解壓縮
- tar -zxvf zookeeper-3.4.12.tar.gz
# 3.重新命名安裝目錄
- mv zookeeper-3.4.12 zk
# 4.配置 zoo.cfg 配置檔案
- 1.修改 ZooKeeper 的 conf 目錄下的 zoo_simple.cfg,修改完後,重新命名為zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/zookeeper/zkdata
clientPort=2181
# 5.啟動 ZooKeeper
- 在 ZooKeeper 的 bin 目錄下,執行 zkServer.sh
./bin/zkServer.sh start /usr/zookeeper/conf/zoo.cfg
# 6.使用 jps 檢視啟動是否成功
# 7.啟動客戶端連線到 ZooKeeper
- ./bin/zkCli.sh -server 192.168.0.220:2181
注意:可以透過 ./bin/zkCli.sh help 檢視客戶端所有可以執行的指令
4.2 Docker 安裝 ZooKeeper
# 1.獲取 ZooKeeper 的映象
- docker pull zookeeper:3.4.14
# 2.啟動 ZooKeeper 服務
- docker run --name zk -p 2181:2181 -d zookeeper:3.4.14
5. 客戶端基本指令
# 1.ls path 檢視特定節點下面的子節點
# 2.create path data 建立一個節點。並給節點繫結資料(預設是永續性節點)
- create path data 建立持久節點(預設是持久節點)
- create -s path data 建立永續性順序節點
- create -e path data 建立臨時性節點(注意:臨時節點不能含有任何子節點)
- create -e -s path data 建立臨時順序節點(注意:臨時節點不能含有任何子節點)
# 3.stat path 檢視節點狀態
# 4.set path data 修改節點資料
# 5.ls2 path 檢視節點下孩子和當前節點的狀態
# 6.history 檢視操作歷史
# 7.get path 獲得節點上繫結的資料資訊
# 8.delete path 刪除節點(注意:刪除節點不能含有子節點)
# 9.rmr path 遞迴刪除節點(注意:會將當前節點下所有節點刪除)
# 10.quit 退出當前會話(會話失效)
6. 節點監聽機制 watch
客戶端可以監測 znode
節點的變化。znode
節點的變化會觸發相應的事件,然後清除對該節點的監測。
當監測一個 znode
節點時候,Zookeeper 會傳送通知給監測節點。一個 Watch 事件是一個一次性的觸發器,當被設定了 Watch 的資料和目錄發生了改變的時候,則伺服器將這個改變傳送給設定了 Watch 的客戶端以便通知它們。
# 1.ls /path true 監聽節點目錄的變化
# 2.get /path true 監聽節點資料的變化
7.Java 操作 ZooKeeper
7.1 建立專案引入依賴
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
7.2 獲取 ZooKeeper 客戶端物件
private ZkClient zkClient;
/**
* 獲取zk客戶端連線
*/
@Before
public void Before() {
// 引數1:伺服器的ip和埠
// 引數2:會話的超時時間
// 引數3:回話的連線時間
// 引數4:序列化方式
zkClient = new ZkClient("192.168.28.132:2181", 30000, 60000, new SerializableSerializer());
}
/**
* 關閉資源
*/
@After
public void after(){
zkClient.close();
}
7.3 常用 API
- 建立節點
/**
* 建立節點
*/
@Test
public void testCreateNode() {
//第一中建立方式 返回建立節點的名稱
String nodeName = zkClient.create("/node5", "lisi", CreateMode.PERSISTENT);
zkClient.create("/node6", "zhangsan", CreateMode.PERSISTENT_SEQUENTIAL);
zkClient.create("/node7", "王五", CreateMode.EPHEMERAL);
zkClient.create("/node8", "xiaozhang", CreateMode.EPHEMERAL_SEQUENTIAL);
//第二種建立方式 不會返回建立節點的名稱
zkClient.createPersistent("/node1", "持久資料");
zkClient.createPersistentSequential("/node1/aa", "持久資料順序節點");
zkClient.createEphemeral("/node2", "臨時節點");
zkClient.createEphemeralSequential("/node1/bb", "臨時順序節點");
}
- 刪除節點
/**
* 刪除節點
*/
@Test
public void testDeleteNode() {
// 刪除沒有子節點的節點 返回值:是否刪除成功
boolean delete = zkClient.delete("/node1");
// 遞迴刪除節點資訊 返回值:是否刪除成功
boolean recursive = zkClient.deleteRecursive("/node1");
}
- 檢視節點的子節點
/**
* 查詢節點
*/
@Test
public void testFindNodes() {
// 獲取指定路徑的節點資訊
// 返回值:為當前節點的子節點資訊
List<String> children = zkClient.getChildren("/");
for (String child : children) {
System.out.println(child);
}
}
- 檢視當前節點的資料
注意:如果出現:
org.I0Itec.zkclient.exception.ZkMarshallingError: java.io.StreamCorruptedException: invalid stream header: 61616161
. 異常的原因是:在 Shell 中的資料序列化方式和 Java 程式碼中使用的序列化方式不一致,因此要解決這個問題只需要保證序列化一致即可。
/**
* 獲取節點的資料
*
*/
@Test
public void testFindNodeData() {
Object readData = zkClient.readData("/node3");
System.out.println(readData);
}
- 檢視當前節點的資料並獲取狀態資訊
/**
* 獲取資料以及當前節點的狀態資訊
*/
@Test
public void testFindNodeDataAndStat() {
Stat stat = new Stat();
Object readData = zkClient.readData("/node60000000024", stat);
System.out.println(readData);
System.out.println(stat);
}
- 修改節點資料
/**
* 修改節點資料
*/
@Test
public void testUpdateNodeData() {
zkClient.writeData("/node60000000024", new User("121", "name", "xxx"));
}
- 監聽節點資料的變化
/**
* 監聽節點資料的變化
*/
@Test
public void testOnNodeDataChange() throws IOException {
zkClient.subscribeDataChanges("/node60000000024", new IZkDataListener() {
// 當節點的值在修改時,會自動呼叫這個方法 將當前修改節點的名字,和節點變化之後的資料傳遞給方法
public void handleDataChange(String nodeName, Object result) throws Exception {
System.out.println(nodeName);
System.out.println(result);
}
// 當節點的值被刪除的時候,會自動呼叫這個方法,會將節點的名字已引數形式傳遞給方法
public void handleDataDeleted(String nodename) throws Exception {
System.out.println("節點的名字:" + nodename);
}
});
//阻塞客戶端
System.in.read();
}
- 監聽節點目錄的變化
/**
* 監聽節點的變化
*/
@Test
public void testOnNodesChange() throws IOException {
zkClient.subscribeChildChanges("/node60000000024", new IZkChildListener() {
// 當節點的發生變化時,會自動呼叫這個方法
// 引數1:父節點名稱
// 引數2:父節點中的所有子節點名稱
public void handleChildChange(String nodeName, List<String> list) throws Exception {
System.out.println("父節點名稱:" + nodeName);
System.out.println("發生變更後位元組孩子節點名稱:");
for (String name : list) {
System.out.println(name);
}
}
});
// 阻塞客戶端
System.in.read();
}
8. ZooKeeper 的叢集
8.1 叢集(Cluster)
# 1.叢集(Cluster)
- 集合同一種軟體服務的多個節點同時提供服務
# 2.叢集解決問題
- 單節點的併發訪問的壓力問題
- 單節點故障問題(如硬體老化、自然災害等)
8.2 叢集架構
8.3 叢集搭建
# 1.建立三個 dataDir
- mkdir zkdata1 zkdata2 zkdata3
# 2.分別在三個dataDir目錄下面myid檔案
- touch ./zkdata1/myid
myid 的內容是 伺服器的 表示 1|2|3
# 3.在 /conf 目錄下建立三個 ZooKeeper 配置檔案,分別為 zoo1.cfg、zoo2.cfg、zoo3.cfg
- zoo1.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/root/zkdata1
clientPort=3001
server.1=10.15.0.5:3002:3003
server.2=10.15.0.5:4002:4003
server.3=10.15.0.5:5002:5003
- zoo2.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/root/zkdata2
clientPort=4001
server.1=10.15.0.5:3002:3003
server.2=10.15.0.5:4002:4003
server.3=10.15.0.5:5002:5003
- zoo3.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/root/zkdata3
clientPort=5001
server.1=10.15.0.5:3002:3003
server.2=10.15.0.5:4002:4003
server.3=10.15.0.5:5002:5003
解釋:
1.server.X: x為伺服器的唯一標識。
2.192.168.0.220: 伺服器所在的ip地址
3.3002: 資料同步使用的埠號
4.3003: 選舉使用的埠號
# 4.分別啟動各個 ZooKeeper 伺服器
- ./bin/zkServer.sh start /usr/zookeeper/conf/zoo1.cfg
- ./bin/zkServer.sh start /usr/zookeeper/conf/zoo2.cfg
- ./bin/zkServer.sh start /usr/zookeeper/conf/zoo3.cfg
# 5.檢視各個 ZooKeeper 伺服器的角色資訊
- ./bin/zkServer.sh status /usr/zookeeper/conf/zoo1.cfg
# 6.客戶端連線任意 ZooKeeper 伺服器進行節點操作
- ./bin/zkCli.sh -server 192.168.0.220:3001
# 7.停止特定 ZooKeeper 伺服器
- ./bin/zkServer.sh stop /usr/zookeeper/conf/zoo1.cfg