ZooKeeper學習之內部原理

這裏學習Zookeeper的選舉機制、節點類型、Stat結構體以及寫數據流程。

【1】選舉機制

Zookeeper適合安裝在奇數臺服務器上,集羣中半數以上的機器存活,則集羣可用。故,又稱之爲半數機制。

Zookeeper雖然在配置文件中並沒有指定Master和Slave,但是Zookeeper集羣工作時是有一個節點爲leader,其他爲follower。leader是通過內部的選舉機制臨時產生的。

假設有五臺服務器組成的Zookeeper集羣,它們的id是1-5,同時它們都是最新啓動的,也就是沒有歷史數據(在存放數據量這一點上,它們都是一樣的)。如下圖所示:
在這裏插入圖片描述
(1) 服務器1啓動,此時只有它一臺服務器啓動了,它發出去的報文沒有響應,所以它的選舉機制一直是LOOKING狀態(它投了自己一票)。

(2)服務器2啓動,它與服務器1進行通信,互相交換自己的選舉結果(服務器2投自己一票,服務器1投服務器2一票,此時服務器2兩票,服務器1一票)。由於兩者都沒有歷史數據,所以id值較大的服務器2勝出。但是由於沒有達到超過半數以上故而leader未產生。

(3)服務器3啓動,先投自己一票,然後與服務器1,2交換選舉結果。由於服務器1 2均未產生leader,故而在服務器3啓動時會投其一票。此時服務器3票數爲3票,已經超過半數,故而晉級爲leader。

(4)服務器4啓動,此時已經有了leader,自動爲follower(先入爲主機制)。

(5)同服務器4一樣,成功leader的小弟。


【2】Zookeeper的節點類型

節點類型根據持久性分爲持久性節點和臨時性節點(短暫存活)。

  • 持久性節點:客戶端和服務端斷開連接後,創建的節點不刪除。
  • 臨時性節點:客戶端和服務端斷開連接後,創建的節點自己刪除。

① 持久化目錄節點

客戶端與Zookeeper斷開連接後,該節點依舊存在。

② 持久化順序編號目錄節點

客戶端與Zookeeper斷開連接後,該節點依舊存在,只是Zookeeper給該節點名稱進行順序編號。

說明:創建znode時設置順序標識,znode名稱後會附加一個值,順序號是一個單調遞增的計數器,由父節點維護。

在分佈式系統中,順序號可以被用於爲所有的事件進行全局排序。這樣客戶端可以根據順序號推斷事件的順序。

③ 臨時目錄節點

客戶端與Zookeeper端口連接後,該節點被刪除。

④ 臨時順序編號目錄節點

客戶端與Zookeeper端口連接後,該節點被刪除,只是Zookeeper給該節點名稱進行順序編號。


【3】客戶端命令行操作

客戶端連接與關閉

連接命令如下所示(在bin目錄下執行):

zkCli.cmd  -server 127.0.0.1:3182

連接成功如下圖(狀態會是CONNECTED):
在這裏插入圖片描述
可以使用命令close關閉連接:

在這裏插入圖片描述

再次使用connect命令進行連接,其會重新變爲CONNECTED狀態:

connect 127.0.0.1:3182

在這裏插入圖片描述

① 創建普通節點

//創建節點時必須同時包含數據
[zk: localhost:2181(CONNECTED) 9] create /jane1 love
Created /jane1
[zk: localhost:2181(CONNECTED) 10] ls /
[jane1, zookeeper]

若是集羣下操作,將會同步到其他server:
在這裏插入圖片描述

② 創建短暫節點

[zk: localhost:2181(CONNECTED) 11] create -e /jane2 love
Created /jane2

當客戶端端口連接後,該節點將不存在。如下當客戶端端口連接後集羣中其他Zookeeper監控情況:

[zk: localhost:2181(CONNECTED) 1] ls /
[jane1, jane2, zookeeper]
[zk: localhost:2181(CONNECTED) 2] ls /
[jane1, zookeeper]

③ 創建帶序號的節點

[zk: localhost:2181(CONNECTED) 0] ls /
[jane1, zookeeper]
[zk: localhost:2181(CONNECTED) 1] create -s /jane2 loave
Created /jane20000000002
[zk: localhost:2181(CONNECTED) 4] create -s /jane1/jane11 love
Created /jane1/jane110000000000

如果路徑下無節點,則序號從0開始。否則序號從節點數量開始。


④ 獲取節點內容

[zk: localhost:2181(CONNECTED) 5] get /jane1
love

⑤ 修改節點數據

[zk: localhost:2181(CONNECTED) 8] set /jane1 "love me"
[zk: localhost:2181(CONNECTED) 9] get /jane1
love me

⑥ 集羣環境下節點值變化監聽

在server2中註冊監聽:

[zk: localhost:2181(CONNECTED) 4] get /jane1 watch
'get path [watch]' has been deprecated. Please use 'get [-s] [-w] path' instead.
love me
[zk: localhost:2181(CONNECTED) 5]
WATCHER::

在server1中修改/jane1的值同時觀察server2變化:
在這裏插入圖片描述

⑦ 監聽節點的子節點變化(路徑變化)

在server2註冊路徑監聽:

[zk: localhost:2181(CONNECTED) 5] ls  /jane1 watch
'ls path [watch]' has been deprecated. Please use 'ls [-w] path' instead.
[jan12, jane11, jane110000000000]
[zk: localhost:2181(CONNECTED) 6] ls -w /jane1
[jan12, jane11, jane110000000000]

在server1中爲/jane1節點創建子節點同時觀察server2變化:
在這裏插入圖片描述
可以發現監聽只有一次有效。


⑧ 刪除節點

delete  /jane1/jane12

⑨ 遞歸刪除節點

rmr  /jane1/jane13

⑩ 查看節點狀態

[zk: localhost:2181(CONNECTED) 23] stat /jane1
cZxid = 0x100000002
ctime = Sun Oct 06 15:36:15 CST 2019
mZxid = 0x10000000d
mtime = Sun Oct 06 15:55:45 CST 2019
pZxid = 0x100000011
cversion = 6
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 4

(11)ls 與ls2區別

二者打印路徑信息分別如下:

//ls2命令是ls命令的增強版,比ls命令多輸出本節點信息
[zk: 127.0.0.1:3182(CONNECTED) 3] ls2 /
'ls2' has been deprecated. Please use 'ls [-s] path' instead.
[zookeeper, vscrm_dev_domain]
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x100000005
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 2

//ls命令用於獲取路徑下的節點信息,注意路徑爲絕對路徑
[zk: 127.0.0.1:3182(CONNECTED) 4] ls /
[vscrm_dev_domain, zookeeper]

更多命令操作參考如下:

ZooKeeper -server host:port cmd args
        addauth scheme auth
        close
        config [-c] [-w] [-s]
        connect host:port
        create [-s] [-e] [-c] [-t ttl] path [data] [acl]
        delete [-v version] path
        deleteall path
        delquota [-n|-b] path
        get [-s] [-w] path
        getAcl [-s] path
        history
        listquota path
        ls [-s] [-w] [-R] path
        ls2 path [watch]
        printwatches on|off
        quit
        reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]
        redo cmdno
        removewatches path [-c|-d|-a] [-l]
        rmr path
        set [-s] [-v version] path data
        setAcl [-s] [-v version] [-R] path acl
        setquota -n|-b val path
        stat [-w] path
        sync path

【4】Stat結構體

節點結構如下所示:

[zk: localhost:2181(CONNECTED) 23] stat /jane1
cZxid = 0x100000002
ctime = Sun Oct 06 15:36:15 CST 2019
mZxid = 0x10000000d
mtime = Sun Oct 06 15:55:45 CST 2019
pZxid = 0x100000011
cversion = 6
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 4

① cZxid

創建節點的事務zxid。每次修改Zookeeper狀態都會收到一個zxid形式的時間戳,也就是Zookeeper事務ID。事務ID是Zookeeper中所有修改總的次序,每個修改都有唯一的zxid。如果zxid1小於zxid2,那麼zxid1在zxid2之前發生。

② ctime

znode被創建的毫秒數(從1970年開始)。

③ mZxid

znode最後更新的事務zxid。

④ mtime

znode最後修改的毫秒數(從1970年開始)。

⑤ pZxid

znode最後更新的子節點zxid。

⑥ cversion

znode子節點變化號,znode子節點修改次數。

⑦ dataVersion

znode數據變化號。

⑧ aclVersion

znode訪問控制列表的變化號。

⑨ ephemeralOwner

如果是臨時節點,這個是znode擁有者的session id。如果不是臨時節點,則是0。

⑩ dataLength

znode的數據長度。

(11) numChildren

znode子節點數量。


【5】Zookeeper監聽器原理

① 監聽器原理詳解

  • 首先要有一個main()線程;
  • 在main線程創建Zookeeper客戶端。這時就會創建兩個線程,一個負責網絡連接通信(connect),一個負責監聽(listener)。
  • 通過connect線程將註冊的監聽事件發送給Zookeeper。
  • 將註冊的監聽事件添加到Zookeeper的註冊監聽器列表中。
  • Zookeeper監聽到有數據或者路徑變化,就會將這個消息發送給listener線程
  • listener線程內部調用了process()方法(其實listener相當於回調函數)。
    在這裏插入圖片描述

② 常見的監聽器

  • 監聽節點數據變化
get path [watch]
get [-s] [-w] path
  • 監聽子節點增減變化
ls2   path   [watch]
ls    path   [watch]
ls    -w      path

【6】Java操作zookeeper

這裏用的原生java,沒有用框架,需要的jar 依賴如下:

<dependency>
    <groupId>org.apache.zookeeper</groupId>
	<artifactId>zookeeper</artifactId>
	<version>3.4.10</version>
</dependency>

① 獲取zookeeper客戶端

private String connectString="127.0.0.1:3181,127.0.0.1:3182,127.0.0.1:3183";
private int sessionTimeout = 2000;
private ZooKeeper zkClient;

@Before
public void init() throws IOException{
	//獲取客戶端對象並註冊監聽
	zkClient = new ZooKeeper(connectString, sessionTimeout , new Watcher() {
		
		public void process(WatchedEvent event) {
			
			System.out.println("---------start----------");
			List<String> children;
			try {
				children = zkClient.getChildren("/", true);

				for (String child : children) {
					System.out.println(child);
				}
				System.out.println("---------end----------");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	});
}

② 創建節點

@Test
public void createNode() throws KeeperException, InterruptedException{
	
	String path = zkClient.create("/janus", "love".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
	
	System.out.println(path);
	
}

③ 獲取子節點,並監控節點的變化

@Test
public void getDataAndWatch() throws KeeperException, InterruptedException{
	
	List<String> children = zkClient.getChildren("/", true);
	//保持監聽線程一直運行
	Thread.sleep(Long.MAX_VALUE);
}

示意圖如下:
在這裏插入圖片描述


④ 判斷節點是否存在

@Test
public void exist() throws KeeperException, InterruptedException{
	
	Stat stat = zkClient.exists("/janus", false);
	
	System.out.println(stat==null? "not exist":"exist");
}

⑤ 刪除節點

@Test
public void deleteNode() throws KeeperException, InterruptedException{
	
	zkClient.delete("/jane2",0);

}

注意,如果節點下有子孩子,則不許刪除。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章