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