目錄
1 Zookeeper
1.1 概述
Zookeeper是一個開源的、分佈式的,爲分佈式應用提供協調服務的Apache項目
Zookeeper從設計模式角度來理解:是一個基於觀察者模式設計的分佈式服務管理框架,他負責存儲和管理大家都關心的數據,然後接受觀察者的註冊,一旦這些數據的狀態發生變化,Zookeeper就將通知已經再zookeeper上註冊的那些觀察者,通知其做出相應的反應,從而實現集羣中類似Master/Slave管理模式
簡單來說:Zookeeper = 文件系統(可以再zk上存儲數據)+ 通知機制
1.2 特點
- zookeeper:一個領導者(leader),多個跟隨者(follower)組成的集羣
- Leader負責進行投票的發起和決議,更新系統狀態
- Follower用於接收客戶端請求,並向客戶端返回結果,在選舉leader過程中參與投票
- 集羣中只要由半數以上節點存活,zookeeper集羣就可以正常工作,提供服務
- 全局數據一致:每個server保存一份相同的數據副本,client無論連接哪一個server,數據都是一致的
- 更新請求按順序進行,來自一個client的更新請求按其發送順序依次進行
- 數據更新原子性,一次數據跟新要麼全部成功,要麼全部失敗
- 實時性,在一定的時間範圍內,client能讀到最新的數據
1.3 數據結構
zookeeper數據模型的結構和Unix文件系統很類似,整體上可以看作爲一棵樹,每個節點稱爲一個ZNode
顯然,zookeeper集羣自身維護了一套數據結構,這個結構就是一個樹形結構,其上的每一個節點成爲“znode”,每個znode節點默認能夠存儲1MB的數據,每個znode節點都可以通過其路徑唯一標識,如圖所示
那zookeeper這顆“樹”有什麼特點呢? zookeeper的節點znode分爲兩種類型:
- 短暫/臨時(ephemeral):當客戶端和服務端斷開連接之後,所創建的znode節點會自動刪除
- 持久(persistent):當客戶端和服務端斷開連接後,所創建的znode節點不會刪除
1.4 應用場景,
提供的服務包括:分佈式消息同步和協調機制、服務器節點動態上下線、統一配置管理、負載均衡、集羣管理等,如圖
1.5 下載地址
官網:https://zookeeper.apache.org (俗稱動物園管理員)
網盤:請點這裏 提取碼:yzpk
2 zookeeper安裝部署
2.1 分佈式安裝部署
1. 集羣規劃
在hadoop101、hadoop102和hadoop103三個節點上部署zookeeper
2. 解壓安裝
將下載的zookeeper文件上傳到虛擬機上
1. 解壓 zookeeper安裝包到/opt/module/目錄下
[root@hadoop101 software]$ tar -zxvf zookeeper-3.4.10.tar.gz -C /opt/module/
2. 在 /opt/module/zookeeper-3.4.10/這個目錄下創建zkData (後面有解釋)
3. 重命名/opt/module/zookeeper-3.4.10/conf這個目錄下的zoo_sample.cfg爲zoo.cfg
3. 配置zoo.cfg文件
1. 具體配置
dataDir=/opt/module/zookeeper-3.4.10/zkData
增加如下配置:
2. 配置參數解讀
server.A=B:C:D
A是一個數字,表示這個是第幾號服務器;
B是這個服務器的ip地址;
C是這個服務器與集羣中的Leader服務器交換信息的端口;
D是萬一集羣中的Leader服務器掛了,需要一個端口來重新進行選舉,選出一個新的Leader,而這個端口就是用來執行選舉時服務器相互通信的端口。
集羣模式下配置一個文件myid,這個文件在dataDir目錄下,這個文件裏面有一個數據就是A的值,Zookeeper啓動時讀取此文件,拿到裏面的數據與zoo.cfg裏面的配置信息比較從而判斷到底是哪個server。
4. 集羣操作
1. 在/opt/module/zookeeper-3.4.10/zkData目錄下創建一個myid的文件,並編輯該文件
touch myid
vim myid 在文件中添加與server對應的編號:如1
2. 拷貝配置好的zookeeper到另外兩臺機器上,並分別修改myid文件中的內容爲2、3
3. 分別啓動zookeeper
[root@hadoop101 zookeeper-3.4.10]# bin/zkServer.sh start
[root@hadoop102 zookeeper-3.4.10]# bin/zkServer.sh start
[root@hadoop103 zookeeper-3.4.10]# bin/zkServer.sh start
4. 查看狀態
[root@hadoop101 zookeeper-3.4.10]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.4.10/bin/../conf/zoo.cfg
Mode: follower
[root@hadoop102 zookeeper-3.4.10]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.4.10/bin/../conf/zoo.cfg
Mode: leader
[root@hadoop103 zookeeper-3.4.5]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.4.10/bin/../conf/zoo.cfg
Mode: follower
2.2 配置參數解讀
解釋zoo.cfg文件中參數含義
1. tickTime: 通信心跳數,Zookeeper服務器心跳時間,單位毫秒
Zookeeper使用的基本時間,服務器之間維持心跳的時間間隔,也就是每個tickTime時間都會發送一個心跳,報活
2. initLinit:LF初始通信時限
集羣中的follower跟隨着服務器(F)與leader領導者服務器(L)之間初始連接時能容忍的最多心跳數,用它來界定集羣中的zookeeper服務器連接到 Leader的時限
3. syncLimit:LF同步通信時限
集羣中的Leader與Follower之間的最大相應時間單位,假如響應超過syncLimit*tickTime,Leader認爲Follower掛掉,從服務器列表中刪除
在運行過程中,Leader負責與ZK集羣中的所有機器進行通信,例如通過一些心跳檢測機制,來檢測機器的存活狀態。
如果Leader發出心跳包在syncLimit之後,還沒有從Follower那收到響應,那麼就認爲i這個Follower已經不在線了
4. dataDir:數據文件目錄+數據持久化路徑
保存內存數據庫快照信息的位置,如果沒有其他說明,更新的事務日誌也保存到數據庫。
5. clientPort:客戶端連接端口
監控客戶端連接的端口
3 Zookeeper內部原理
3.1 選舉機制
1. 半數機制:集羣中半數以上存活,集羣可用,否則集羣處於癱瘓狀態不可用。
爲什麼說zookeeper適合裝在奇數臺機器上 ?
原因:比如三個節點的zookeeper集羣,要想保證集羣可用,最多掛掉一個節點;如果是四個節點的zookeepe集羣,要想保證集羣可用,最多也只能掛掉一個節點,如果掛掉兩個就不滿足半數以上存活。所以說奇數個節點和偶數個節點對於zookeeper節點宕機的容忍度是相同的,所以選用奇數個節點,避免浪費資源
2. 選舉機制:
zookeeper雖然在配置文件中並沒有指定leader和follower。但是在zookeeper集羣工作時,是有一個節點爲leader,其他節點爲follower,Leader是通過內部的選舉機制臨時產生的
選舉機制以下面的例子說明
假設有五臺服務器組成的zookeeper集羣,它們的id從1-5,同時它們都是最新啓動的。假設這些服務器依序啓動,來看看會發生什麼
(1)服務器1啓動,此時只有它一臺服務器啓動了,它發出去的報沒有任何響應,所以它的選舉狀態一直是LOOKING狀態。
(2)服務器2啓動,它與最開始啓動的服務器1進行通信,互相交換自己的選舉結果,由於兩者都沒有歷史數據,所以id值較大的服務器2勝出,但是由於沒有達到超過半數以上的服務器都同意選舉它(這個例子中的半數以上是3),所以服務器1、2還是繼續保持LOOKING狀態。
(3)服務器3啓動,根據前面的理論分析,服務器3成爲服務器1、2、3中的老大,而與上面不同的是,此時有三臺服務器選舉了它,所以它成爲了這次選舉的leader。
(4)服務器4啓動,根據前面的分析,理論上服務器4應該是服務器1、2、3、4中最大的,但是由於前面已經有半數以上的服務器選舉了服務器3,所以它只能接收當小弟的命了。
(5)服務器5啓動,同4一樣當小弟。
3.2 節點類型
1. Znode有兩種類型四種形式的目錄節點(默認是persistent)
(1)持久化目錄節點(PERSISTENT)
客戶端與zookeeper斷開連接後,該節點依舊存在
(2)持久化順序編號目錄節點(PERSISTENT_SEQUENTIAL)
客戶端與zookeeper斷開連接後,該節點依舊存在,只是Zookeeper給該節點名稱進行順序編號
(3)臨時目錄節點(EPHEMERAL)
客戶端與zookeeper斷開連接後,該節點被刪除
(4)臨時順序編號目錄節點(EPHEMERAL_SEQUENTIAL)
客戶端與zookeeper斷開連接後,該節點被刪除,只是Zookeeper給該節點名稱進行順序編號
2.創建znode時設置順序標識,znode名稱後會附加一個值,順序號是一個單調遞增的計數器,由父節點維護
3.在分佈式系統中,順序號可以被用於爲所有的事件進行全局排序,這樣客戶端可以通過順序號推斷事件的順序
3.3 監聽原理
1. 監聽原理
- 首先要有一個main()線程
- 在main線程中創建Zookeeper客戶端,這時就會創建兩個線程,一個負責網絡連接通信(connect),一個負責監聽(listener)。
- 通過connect線程將註冊的監聽事件發送給Zookeeper。
- 在Zookeeper的註冊監聽器列表中將註冊的監聽事件添加到列表中。
- Zookeeper監聽到有數據或路徑變化,就會將這個消息發送給listener線程。
- listener線程內部調用了process()方法。
2. 常見的監聽
1. 監聽節點數據的變化
get path [watch]
2. 監聽節點增減的變化
ls path [watch]
3.5 寫數據流程
ZooKeeper 的寫數據流程主要分爲以下幾步:
- 1)比如 Client 向 ZooKeeper 的 Server1 上寫數據,發送一個寫請求。
- 2)如果Server1不是Leader,那麼Server1 會把接受到的請求進一步轉發給Leader,因爲每個ZooKeeper的Server裏面有一個是Leader。這個Leader 會將寫請求廣播給各個Server,比如Server1和Server2, 各個Server寫成功後就會通知Leader。
- 3)當Leader收到大多數 Server 數據寫成功了,那麼就說明數據寫成功了。如果這裏三個節點的話,只要有兩個節點數據寫成功了,那麼就認爲數據寫成功了。寫成功之後,Leader會告訴Server1數據寫成功了。
- 4)Server1會進一步通知 Client 數據寫成功了,這時就認爲整個寫操作成功。
4 zookeeper實戰
4.1 客戶端命令行操作
命令基本語法 |
功能描述 |
help |
顯示所有操作命令 |
ls path [watch] |
使用 ls 命令來查看當前znode中所包含的內容 |
ls2 path [watch] |
查看當前節點數據並能看到更新次數等數據 |
create |
普通創建 -s 含有序列 -e 臨時(重啓或者超時消失) |
get path [watch] |
獲得節點的值 |
set |
設置節點的具體值 |
stat |
查看節點狀態 |
delete |
刪除節點 |
rmr |
遞歸刪除節點 |
1. 啓動客戶端
[root@hadoop101 zookeeper-3.4.10]$ bin/zkCli.sh
2. 顯示所有操作命令
[zk: localhost:2181(CONNECTED) 1] help
3. 查看當前znode中所包含的內容
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]
4.查看當前節點數據並能看到更新次數等數據
[zk: localhost:2181(CONNECTED) 1] ls2 /
[zookeeper]
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
5.創建普通節點
[zk: localhost:2181(CONNECTED) 2] create /app1 "hello app1"
Created /app1
[zk: localhost:2181(CONNECTED) 4] create /app1/server101 "192.168.1.101"
Created /app1/server101
6.獲得節點的值
[zk: localhost:2181(CONNECTED) 6] get /app1
hello app1
cZxid = 0x20000000a
ctime = Mon Jul 17 16:08:35 CST 2017
mZxid = 0x20000000a
mtime = Mon Jul 17 16:08:35 CST 2017
pZxid = 0x20000000b
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 10
numChildren = 1
[zk: localhost:2181(CONNECTED) 8] get /app1/server101
192.168.1.101
cZxid = 0x20000000b
ctime = Mon Jul 17 16:11:04 CST 2017
mZxid = 0x20000000b
mtime = Mon Jul 17 16:11:04 CST 2017
pZxid = 0x20000000b
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 13
numChildren = 0
7.創建短暫節點
[zk: localhost:2181(CONNECTED) 9] create -e /app-emphemeral 8888
(1)在當前客戶端是能查看到的
[zk: localhost:2181(CONNECTED) 10] ls /
[app1, app-emphemeral, zookeeper]
(2)退出當前客戶端然後再重啓客戶端
[zk: localhost:2181(CONNECTED) 12] quit
[bigdata@hadoop104 zookeeper-3.4.10]$ bin/zkCli.sh
(3)再次查看根目錄下短暫節點已經刪除
[zk: localhost:2181(CONNECTED) 0] ls /
[app1, zookeeper]
8.創建帶序號的節點
(1)先創建一個普通的根節點app2
[zk: localhost:2181(CONNECTED) 11] create /app2 "app2"
(2)創建帶序號的節點
[zk: localhost:2181(CONNECTED) 13] create -s /app2/aa 888
Created /app2/aa0000000000
[zk: localhost:2181(CONNECTED) 14] create -s /app2/bb 888
Created /app2/bb0000000001
[zk: localhost:2181(CONNECTED) 15] create -s /app2/cc 888
Created /app2/cc0000000002
如果原節點下有1個節點,則再排序時從1開始,以此類推。
[zk: localhost:2181(CONNECTED) 16] create -s /app1/aa 888
Created /app1/aa0000000001
9.修改節點數據值
[zk: localhost:2181(CONNECTED) 2] set /app1 999
10.節點的值變化監聽
(1)在103主機上註冊監聽/app1節點數據變化
[zk: localhost:2181(CONNECTED) 26] get /app1 watch
(2)在102主機上修改/app1節點的數據
[zk: localhost:2181(CONNECTED) 5] set /app1 777
(3)觀察103主機收到數據變化的監聽
WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/app1
11.節點的子節點變化監聽(路徑變化)
(1)在103主機上註冊監聽/app1節點的子節點變化
[zk: localhost:2181(CONNECTED) 1] ls /app1 watch
[aa0000000001, server101]
(2)在102主機/app1節點上創建子節點
[zk: localhost:2181(CONNECTED) 6] create /app1/bb 666
Created /app1/bb
(3)觀察103主機收到子節點變化的監聽
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/app1
12.刪除節點
[zk: localhost:2181(CONNECTED) 4] delete /app1/bb
13.遞歸刪除節點
[zk: localhost:2181(CONNECTED) 7] rmr /app2
14.查看節點狀態
[zk: localhost:2181(CONNECTED) 12] stat /app1
cZxid = 0x20000000a
ctime = Mon Jul 17 16:08:35 CST 2017
mZxid = 0x200000018
mtime = Mon Jul 17 16:54:38 CST 2017
pZxid = 0x20000001c
cversion = 4
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 2
4.2 stat結構體
- 1)czxid- 引起這個znode創建的zxid,創建節點的事務的zxid
- 每次修改ZooKeeper狀態都會收到一個zxid形式的時間戳,也就是ZooKeeper事務ID。
- 事務ID是ZooKeeper中所有修改總的次序。每個修改都有唯一的zxid,如果zxid1小於zxid2,那麼zxid1在zxid2之前發生。
- 2)ctime - znode被創建的毫秒數(從1970年開始)
- 3)mzxid - znode最後更新的zxid
- 4)mtime - znode最後修改的毫秒數(從1970年開始)
- 5)pZxid-znode最後更新的子節點zxid
- 6)cversion - znode子節點變化號,znode子節點修改次數
- 7)dataversion - znode數據變化號
- 8)aclVersion - znode訪問控制列表的變化號
- 9)ephemeralOwner- 如果是臨時節點,這個是znode擁有者的session id。如果不是臨時節點則是0。
- 10)dataLength- znode的數據長度
- 11)numChildren - znode子節點數量
4.3 API應用
首先導入下面的依賴
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper --> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.10</version> </dependency>
package com.zookeeper;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
public class ZkDemo {
private String connectStr = "hadoop101:2181,hadoop102:2181,hadoop103:2181";
private int sessionTimeout = 20000;
private ZooKeeper zkClient;
/**
* 創建zookeeper客戶端
*/
@Before
public void testZk() throws Exception {
zkClient = new ZooKeeper(connectStr, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("event type:" + watchedEvent.getType() + " path:" + watchedEvent.getPath());
// 獲取子節點信息的同時,開啓對該節點的監控(監控子節點增加或者刪除)
List<String> children = null;
try {
children = zkClient.getChildren("/", true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* 創建子節點
* @throws Exception
*/
@Test
public void testCreateNode() throws Exception {
zkClient.create("/ceshi","testValue".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
/**
* 創建子節點並監聽
* @throws Exception
*/
@Test
public void testGetChildren() throws Exception {
// 獲取子節點信息的同時,開啓對該節點的監控(監控子節點增加或者刪除)
List<String> children = zkClient.getChildren("/", true);
for(String child: children) {
System.out.println("=========>child:" + child);
}
// 線程阻塞
Thread.sleep(Integer.MAX_VALUE);
}
/**
* 判斷節點是否存在
* @throws Exception
*/
@Test
public void testExists() throws Exception {
Stat exists = zkClient.exists("/app1", false);
System.out.println(exists);
}
}