zookeeper使用詳解(命令、客戶端、源碼)


1. zookeeper使用詳解(命令、客戶端、源碼)

1.1. 前言

  zookeeper我們常用來做分布式協調中間件,很多時候我們都接觸不到它的原理和用法,我對他的了解也僅限於知道它可以做分布式協調、配置管理、分布式鎖,並且有個watch節點監聽常常能聽到。接下來我要系統的學下zookeeper的功能和原理,一起走進zookeeper的世界

1.2. 概述

  zookeeper主要目的就是為了分布式應用提供協同服務,zookeeper的節點管理機制,當節點發生變化時(創建、刪除、數據變更),可以通知各個客戶端,利用這種特性,zk的主要場景就如我前面說的:

1. 統一配置:把配置放在ZooKeeper的節點中維護,當配置變更時,客戶端可以收到變更的通知,並應用最新的配置。
2. 集群管理:集群中的節點,創建ephemeral的節點,一旦斷開連接,ephemeral的節點會消失,其它的集群機器可以收到消息。
3. 分布式鎖:多個客戶端發起節點創建操作,只有一個客戶端創建成功,從而獲得鎖。

1.3. zk基本操作

  1. ZNode ZNode是ZK樹形結構的一個節點,它可以包含或者不包含數據。
  2. ZK提供了如下API,用於操作ZNode。
    1. create path data
    2. delete path data
    3. exists path
    4. getdata path
    5. putdata path data
    6. getChildren path
  3. ZK客戶端通過建立一個Session會話,來連接ZK服務,通過這些API來操作ZNode。

1.4. zk模式

  1. ZNode模式 目前ZNode包含持久模式和短暫模式ephemeral。
  2. ephemeral模式指的是這個節點在session斷了之后就會消失
  3. 持久模式和ephemeral模式外,ZNode還可以是有序的(自動分配自增ID到節點上,比如task-1,task-2,task-3)
  4. 因此ZNode一共有四種形態:
    1. 持久
    2. 持久有序
    3. ephemeral
    4. ephemeral有序

1.5. watch機制

  1. Watch和Notifications Watch可以避免每次主動去請求數據是否變化,而是當ZNode變化時,來通知。
  2. Watch是個一次性操作,每次收到通知后,必須重新watch,如果時間比較久或者ZNode更新頻繁,在此時間之間,可能會有更新沒有被通知到(還沒來得急watch)。
  3. ZNode的創建、刪除和修改都可以被watch到。

1.6. FAQ

1.6.1. 客戶端對服務器列表的輪詢機制

  1. 客戶端建立與zk會話的時候需要我們填寫zk服務器列表,之后該列表會被隨機打散然后每次請求輪詢該列表,這種打散是一次性的,之后每次都是這個順序
  2. 知道客戶端輪詢原理,可以知道列表是可以重復填寫的,這樣也可以通過重復填寫配置地址來增加權重,但也會有風險,可能使server切換耗時過長,倒是session_expired

參考 https://blog.51cto.com/nileader/932948

1.6.2. 客戶端常遇到的浴場Connectionloss(連接斷開)和Sessionexpired(session過期)處理

  1. 在ZooKeeper中,服務器和客戶端之間維持的是一個長連接,在 SESSION_TIMEOUT 時間內,服務器會確定客戶端是否正常連接(客戶端會定時向服務器發送heart_beat),服務器重置下次SESSION_TIMEOUT時間。因此,在正常情況下,Session一直有效,並且zk集群所有機器上都保存這個Session信息。在出現問題情況下,客戶端與服務器之間連接斷了(客戶端所連接的那台zk機器掛了,或是其它原因的網絡閃斷),這個時候客戶端會主動在地址列表(初始化的時候傳入構造方法的那個參數connectString)中選擇新的地址進行連接
  2. connectionloss通常發生在連接的zk掛了,這個時候只要等待客戶端連接上新的zk機器(zk必須集群),然后確認操作是否執行成功
  3. sessionexpired通常發生在zk客戶端和服務器的連接斷了,視圖連上新的zk機器,如果這個過程耗時過長,超過session_timeout時間,那么服務器認為這個session已經結束(服務器無法確認時因為其他異常原因還是客戶端主動結束會話),開始清除和這個會話相關的信息,包括會話創建的臨時節點和注冊的watcher。這時客戶端重新連接上服務器,服務器會報sessionexpired。這個時候解決辦法要看業務情況了,只能重新實例化zk對象,重新操作節點數據

1.6.3. 創建的臨時節點什么時候會被刪除,是連接一斷就刪除嗎?

  1. 連接斷了之后,ZK不會馬上移除臨時數據,只有當SESSIONEXPIRED之后,才會把這個會話建立的臨時數據移除。因此,用戶需要謹慎設置Session_TimeOut

1.6.4. zk日志清理

  1. zk不會自動清理日志,參考:https://blog.51cto.com/nileader/932156

1.7. zkClient命令行(包含了全部命令)

1.7.1. 創建節點

  1. 語法create [-s] [-e] path data acl

  2. -s 創建有序節點 -e創建臨時節點

  3. acl專門一節講

    [zk: localhost:2181(CONNECTED) 4] create -s -e /mynode/subnode hellp
    Node does not exist: /mynode/subnode
    [zk: localhost:2181(CONNECTED) 5] create -s -e /mynode/ hellp       
    Node does not exist: /mynode/
    [zk: localhost:2181(CONNECTED) 6] create -s -e /mynode hellp 
    Created /mynode0000000001
    [zk: localhost:2181(CONNECTED) 7] create -s -e /mynode/subnode hello
    Node does not exist: /mynode/subnode
    [zk: localhost:2181(CONNECTED) 8] ls /mynode
    Node does not exist: /mynode
    [zk: localhost:2181(CONNECTED) 9] ls /
    [mycat, mynode0000000001, zookeeper]
    [zk: localhost:2181(CONNECTED) 10] create -s -e /mynode0000000001/subnode hello
    Ephemerals cannot have children: /mynode0000000001/subnode
    
    
  4. 上面的命令可以看出

    1. 第一不能直接創建多級幾點
    2. 第二創建臨時節點不能有子節點
    3. 第三有序節點節點名后會加上序號

1.7.2. 列出節點 ls

  1. ls path [watch]
  2. ls2 path [watch]
[zk: localhost:2181(CONNECTED) 17] ls2 /persistence
[]
cZxid = 0x12
ctime = Tue Mar 26 06:52:28 GMT 2019
mZxid = 0x12
mtime = Tue Mar 26 06:52:28 GMT 2019
pZxid = 0x12
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0
[zk: localhost:2181(CONNECTED) 18] ls /persistence 
[]
  1. 可以看出,ls2能給出更詳細的路徑信息
[zk: localhost:2181(CONNECTED) 2] ls / 1
[persistence, temporary, mycat, zookeeper]
[zk: localhost:2181(CONNECTED) 3] create -e /temp 123

WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/
Created /temp
[zk: localhost:2181(CONNECTED) 4] create -e /temp2 123
Created /temp2
  1. 根節點創建監聽器1,之后根節點的改變會觸發監聽器,但只有一次

1.7.3. 獲取節點信息 get

  1. get path [watch]
[zk: localhost:2181(CONNECTED) 5] get /temp2
123
cZxid = 0x17
ctime = Tue Mar 26 06:59:20 GMT 2019
mZxid = 0x17
mtime = Tue Mar 26 06:59:20 GMT 2019
pZxid = 0x17
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x103c831d4dc0003
dataLength = 3
numChildren = 0

  1. 可以看到,get到路徑有詳細信息,和ls2獲得的信息一樣
  2. 每一個對znode樹的更新操作,都會被賦予一個全局唯一的ID,我們稱之為zxid(ZooKeeper Transaction ID)
  3. 更新操作的ID按照發生的時間順序升序排序。例如,z1大於z2,那么z1的操作就早於z2操作。
  4. 每個 znode 的狀態信息包含以下內容:
    • czxid,創建(create)該 znode 的 zxid
    • mzxid,最后一次修改(modify)該 znode 的 zxid
    • pzxid,最后一次修改該 znode 子節點的 zxid
    • ctime,創建該 znode 的時間
    • mtime,最后一次修改該 znode 的時間
    • dataVersion,該節點內容的版本,每次修改內容,版本都會增加
    • cversion,該節點子節點的版本
    • aclVersion,該節點的 ACL 版本
    • ephemeralOwner,如果該節點是臨時節點(ephemeral node),會列出該節點所在客戶端的 session id;如果不是臨時節點,該值為 0
    • dataLength,該節點存儲的數據長度
    • numChildren,該節點子節點的個數

1.7.4. 檢查狀態 stat

  1. stat path [watch]
  2. 與 get 的區別是,不會列出 znode 的值。

1.7.5. 修改節點 set

  1. set path data [version]
  2. 修改已經存在的節點的值
[zk: localhost:2181(CONNECTED) 10] set /temp2 456
cZxid = 0x17
ctime = Tue Mar 26 06:59:20 GMT 2019
mZxid = 0x18
mtime = Tue Mar 26 07:12:27 GMT 2019
pZxid = 0x17
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x103c831d4dc0003
dataLength = 3
numChildren = 0
  1. 可以看到,在修改節點值之后,mZxid、mtime、dataVersion 都發生了變化。

1.7.6. 刪除節點 rmr

  1. rmr path
[zk: localhost:2181(CONNECTED) 12] rmr /temp2
[zk: localhost:2181(CONNECTED) 13] get /temp2
Node does not exist: /temp2

  1. 刪除 /mynode,不會返回任何內容。如果有子節點的時候,連帶子節點也一起刪除

1.7.7. 刪除節點 delete

  1. delete path [version]
  2. 調用delete和set操作時,如果指定znode版本號,需要與當前的版本號匹配。如果版本號不匹配,操作將會失敗。失敗的原因可能是在我們提交之前,該znode已經被修改過了,版本號發生了增量變化。如果不指定版本號,就是直接操作最新版本的 znode。
  3. 如果要刪除的節點有子節點,不能刪除
[zk: localhost:2181(CONNECTED) 17] create /per 1
Created /per
[zk: localhost:2181(CONNECTED) 18] create /per/subper 2
Created /per/subper
[zk: localhost:2181(CONNECTED) 19] delete /per
Node not empty: /per
[zk: localhost:2181(CONNECTED) 20] delete /per/subper
[zk: localhost:2181(CONNECTED) 21] delete /per
[zk: localhost:2181(CONNECTED) 22] ls /per
Node does not exist: /per

1.7.8. 歷史記錄 history

  1. history 列出最近的10條歷史記錄
[zk: localhost:2181(CONNECTED) 23] history
13 - get /temp2
14 - ls /
15 - ls /temp
16 - get /temp
17 - create /per 1
18 - create /per/subper 2
19 - delete /per
20 - delete /per/subper
21 - delete /per
22 - ls /per
23 - history

1.7.9. 重復之前的命令 redo

  1. redo cmdno 根據 cmdno 重復之前的命令,cmdno 就是方括號里面最后的數字,每次執行命令都會自增。
[zk: localhost:2181(CONNECTED) 25] redo 22
Node does not exist: /per
[zk: localhost:2181(CONNECTED) 26] redo 17
Created /per

1.7.10. 是否輸出 watch 事件(printwatches)

  1. printwatches on|off
[zk: localhost:2181(CONNECTED) 28] printwatches
printwatches is on
[zk: localhost:2181(CONNECTED) 29] ls /mynode
Node does not exist: /mynode
[zk: localhost:2181(CONNECTED) 30] create /mynode 123
Created /mynode
[zk: localhost:2181(CONNECTED) 31] ls /mynode watch
[]
[zk: localhost:2181(CONNECTED) 34] create /mynode/subnode 234

WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/mynode
Created /mynode/subnode
[zk: localhost:2181(CONNECTED) 35] printwatches off
[zk: localhost:2181(CONNECTED) 36] ls /mynode 2
[subnode]
[zk: localhost:2181(CONNECTED) 37] create /mynode/subnode2 567
Created /mynode/subnode2

  1. 可以看到設置off后,監聽打印輸出就沒有了

1.7.11. 關閉連接 close

  1. close
[zk: localhost:2181(CONNECTED) 38] close 
2019-03-26 07:26:59,240 [myid:] - INFO  [main:ZooKeeper@693] - Session: 0x103c831d4dc0003 closed
[zk: localhost:2181(CLOSED) 39] 2019-03-26 07:26:59,241 [myid:] - INFO  [main-EventThread:ClientCnxn$EventThread@522] - EventThread shut down for session: 0x103c831d4dc0003
ls
Not connected
[zk: localhost:2181(CLOSED) 40] ls /
Not connected

1.7.12. 打開連接 connect

  1. connect host:port
[zk: localhost:2181(CLOSED) 42] connect
2019-03-26 07:28:18,093 [myid:] - INFO  [main:ZooKeeper@442] - Initiating client connection, connectString=localhost:2181 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@782830e
[zk: localhost:2181(CONNECTING) 43] 2019-03-26 07:28:18,096 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@1029] - Opening socket connection to server localhost/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
2019-03-26 07:28:18,097 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@879] - Socket connection established to localhost/127.0.0.1:2181, initiating session
2019-03-26 07:28:18,100 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@1303] - Session establishment complete on server localhost/127.0.0.1:2181, sessionid = 0x103c831d4dc0004, negotiated timeout = 30000

[zk: localhost:2181(CONNECTED) 43] ls /
[mycat, mynode, zookeeper, persistence, per]

  1. 可以看到默認連接的是本地的2181

1.7.13. 退出連接 quit

  1. quit 直接退出當前的 zkCli 命令行。

1.7.14. 強制同步 sync

  1. sync path
  2. sync方法會強制客戶端所連接的服務器狀態與leader的狀態同步,這樣在讀取 path 的值就是最新的值了

1.7.15. ACL 操作

  1. 一個znode中不僅包含了存儲的數據,還有 ACL(Access Control List)。znode的創建時,可以給它設置一個ACL(Access Control List),來決定誰可以對znode做哪些操作
  2. ACL 具有以下特點:
    • ZooKeeper的權限控制是基於每個znode節點的,需要對每個節點設置權限
    • 每個znode支持設置多種權限控制方案和多個權限
    • 子節點不會繼承父節點的權限,客戶端無權訪問某節點,但可能可以訪問它的子節點
    • 所以任何一個客戶端都可以通過exists 操作來獲得任何znode的狀態,從而得知znode是否真的存在。

1.7.16. ACL Permissions

ACL 權限 ACL 簡寫 允許的操作
CREATE c 創建子節點
READ r 獲取節點的數據和它的子節點
WRITE w 設置節點的數據
DELETE d 刪除子節點 (僅下一級節點)
ADMIN a 設置 ACL 權限

1.7.17. 權限相關命令

命令 語法 描述
getAcl getAcl path 讀取ACL權限
setAcl setAcl path acl 設置ACL權限
addauth addauth scheme auth 添加認證用戶
create create [-s] [-e] path data acl 創建節點時指明 ACL 權限

1.7.18. ACL Schemes

  1. ZooKeeper內置了一些權限控制方案,可以用以下方案為每個節點設置權限:
方案 描述
world 只有一個用戶:anyone,代表所有人(默認)
ip 使用IP地址認證
auth 使用已添加認證的用戶認證
digest 使用“用戶名:密碼”方式認證
  1. ACL是由鑒權方式、鑒權方式的ID和一個許可(permession)的集合組成。例如,我們想通過一個ip地址為10.0.0.1的客戶端訪問一個znode。那么,我們需要為znode設置一個ACL,鑒權方式使用IP鑒權方式,鑒權方式的ID為10.0.0.1,只允許讀權限。那么 ACL 的格式就是:ip:10.0.0.1:w

1.7.18.1. world 方案

  1. setAcl <path> world:anyone:<acl>
[zk: localhost:2181(CONNECTED) 7] getAcl /world
'world,'anyone
: cdrwa
[zk: localhost:2181(CONNECTED) 8] setAcl /world world:anyone:cdr
cZxid = 0x27
ctime = Tue Mar 26 07:41:43 GMT 2019
mZxid = 0x27
mtime = Tue Mar 26 07:41:43 GMT 2019
pZxid = 0x27
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 1
numChildren = 0
[zk: localhost:2181(CONNECTED) 9] set /world 234
Authentication is not valid : /world

  1. 可以看出,在修改權限為 cdr 之后,不能再設置節點數據了。注意 aclVersion 也發生了變化

1.7.18.2. IP 方案

  1. setAcl <path> ip:<ip>:<acl>
[zk: localhost:2181(CONNECTED) 10] create /ip hello
Created /ip
[zk: localhost:2181(CONNECTED) 11] getAcl /ip    
'world,'anyone
: cdrwa
[zk: localhost:2181(CONNECTED) 12] setAcl /ip ip:52.231.163.100:cdrwa
cZxid = 0x2a
ctime = Tue Mar 26 07:56:21 GMT 2019
mZxid = 0x2a
mtime = Tue Mar 26 07:56:21 GMT 2019
pZxid = 0x2a
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 13] getAcl /ip                        
'ip,'52.231.163.100
: cdrwa
[zk: localhost:2181(CONNECTED) 14] get /ip
Authentication is not valid : /ip

1.7.18.3. auth 方案

addauth digest <user>:<password> #添加認證用戶
setAcl <path> auth:<user>:<acl>
[zk: localhost:2181(CONNECTED) 15] create /auth hello
Created /auth
[zk: localhost:2181(CONNECTED) 16] addauth digest admin:tom
[zk: localhost:2181(CONNECTED) 17] setAcl /auth auth:tom:cdrwa
cZxid = 0x2c
ctime = Tue Mar 26 08:04:23 GMT 2019
mZxid = 0x2c
mtime = Tue Mar 26 08:04:23 GMT 2019
pZxid = 0x2c
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 18] getAcl /auth
'digest,'admin:cFk4QI8k/ZVgHVEnb06Vtoc651o=
: cdrwa


斷開以后再連上,需要重新認證

WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0] get /auth
Authentication is not valid : /auth

1.7.18.4. digest 方案

  1. setAcl <path> digest:<user>:<password>:<acl>
  2. 這里的密碼是經過SHA1及BASE64處理的密文,在SHELL中可以通過以下命令計算:

echo -n : | openssl dgst -binary -sha1 | openssl base64

[root@izbp1itlw36onyj4m9b4hiz ~]# echo -n admin:123456 | openssl dgst -binary -sha1 | openssl base64
0uek/hZ/V9fgiM35b0Z2226acMQ=

[zk: localhost:2181(CONNECTED) 21] create /digest hello
Created /digest
[zk: localhost:2181(CONNECTED) 22] setAcl /digest digest:admin:0uek/hZ/V9fgiM35b0Z2226acMQ=:cdrw 
cZxid = 0x39
ctime = Tue Mar 26 08:22:04 GMT 2019
mZxid = 0x39
mtime = Tue Mar 26 08:22:04 GMT 2019
pZxid = 0x39
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 23] getAcl /digest
'digest,'admin:0uek/hZ/V9fgiM35b0Z2226acMQ=
: cdrw
[zk: localhost:2181(CONNECTED) 24] get /digest
Authentication is not valid : /digest
[zk: localhost:2181(CONNECTED) 25] addauth digest admin:123456
[zk: localhost:2181(CONNECTED) 26] get /digest
hello
cZxid = 0x39
ctime = Tue Mar 26 08:22:04 GMT 2019
mZxid = 0x39
mtime = Tue Mar 26 08:22:04 GMT 2019
pZxid = 0x39
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0

1.7.18.5. 創建節點時指定 ACL

[zk: localhost:2181(CONNECTED) 27] addauth digest admin:tim
[zk: localhost:2181(CONNECTED) 28] create /createnode hello auth:tim:cdrwa  
Created /createnode
[zk: localhost:2181(CONNECTED) 29] getAcl
[zk: localhost:2181(CONNECTED) 30] getAcl /createnode
'digest,'admin:0uek/hZ/V9fgiM35b0Z2226acMQ=
: cdrwa
'digest,'admin:H4JbicQawMpoqvA2LI0LFNFSMNE=
: cdrwa
[zk: localhost:2181(CONNECTED) 31] get /createnode
hello
cZxid = 0x3b
ctime = Tue Mar 26 08:27:27 GMT 2019
mZxid = 0x3b
mtime = Tue Mar 26 08:27:27 GMT 2019
pZxid = 0x3b
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 32] close

[zk: localhost:2181(CLOSED) 33] connect
[zk: localhost:2181(CONNECTED) 34] get /createnode
Authentication is not valid : /createnode
[zk: localhost:2181(CONNECTED) 35] getAcl /createnode
'digest,'admin:0uek/hZ/V9fgiM35b0Z2226acMQ=
: cdrwa
'digest,'admin:H4JbicQawMpoqvA2LI0LFNFSMNE=
: cdrwa
[zk: localhost:2181(CONNECTED) 36] addauth digest admin:tim
[zk: localhost:2181(CONNECTED) 37] get /createnode
hello
cZxid = 0x3b
ctime = Tue Mar 26 08:27:27 GMT 2019
mZxid = 0x3b
mtime = Tue Mar 26 08:27:27 GMT 2019
pZxid = 0x3b
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0

  1. 關閉會話后需要重新授權

1.7.19. zookeeper quota

  1. zookeeper quota 機制支持節點個數(namespace)和空間大小(bytes)的設置。
  2. zookeeper quota 保存在 /zookeeper/quota 節點下,可以設置該節點的 ACL 權限,以防其他人修改。
  3. 語法listquota pathsetquota -n|-b val pathdelquota [-n|-b] path
  4. -n表示設置znode count限制,包含自身節點
[zk: localhost:2181(CONNECTED) 10] ls /zookeeper/quota
[]
[zk: localhost:2181(CONNECTED) 11] get /zookeeper/quota

cZxid = 0x0
ctime = Thu Jan 01 00:00:00 GMT 1970
mZxid = 0x0
mtime = Thu Jan 01 00:00:00 GMT 1970
pZxid = 0x0
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 0
[zk: localhost:2181(CONNECTED) 12] listquota /persistence
absolute path is /zookeeper/quota/persistence/zookeeper_limits
quota for /persistence does not exist.
[zk: localhost:2181(CONNECTED) 13] setquota -n 3 /persistence
Comment: the parts are option -n val 3 path /persistence
[zk: localhost:2181(CONNECTED) 14] create /persistence/node1 123
Created /persistence/node1
[zk: localhost:2181(CONNECTED) 15] create /persistence/node2 124
Created /persistence/node2
[zk: localhost:2181(CONNECTED) 16] create /persistence/node3 125
Created /persistence/node3
[zk: localhost:2181(CONNECTED) 17] listquota /persistence
absolute path is /zookeeper/quota/persistence/zookeeper_limits
Output quota for /persistence count=3,bytes=-1
Output stat for /persistence count=4,bytes=12

  1. 可以看到,超出節點數zk也不會報錯,只會在listquato里打下日志
[zk: localhost:2181(CONNECTED) 18] delquota -n /persistence
[zk: localhost:2181(CONNECTED) 19] listquota /persistence  
absolute path is /zookeeper/quota/persistence/zookeeper_limits
Output quota for /persistence count=-1,bytes=-1
Output stat for /persistence count=4,bytes=12
  1. 刪除節點quota,count變成-1

1.8. java操作

1.8.1. 異步操作

  1. 主流異步操作

參考 https://github.com/llohellohe/zookeeper/blob/master/src/main/java/yangqi/zookeeper/example/masterworker/AsynMaster.java

1.8.2. 狀態變更

  1. Watcher優勢 通過watcher,可以避免主動輪詢導致的額外負擔,更加實時和有效率。
  2. Watcher接口,僅有一個實現接口public void process(WatchedEvent event)
  3. WatchedEvent代表watcher到的事件,它包含發生了什么事件,ZooKeeper的當前連接狀態以及產生事件的ZNode路徑
    1. KeeperState
    2. EventType
    3. path
  4. KeeperState包含Disconnected\SyncConnected\AuthFailed\ConnectedReadOnly\SaslAuthenticated\Expired等6種狀態。
  5. EventType包含五種狀態:
    1. None
    2. NodeCreated
    3. NodeDeleted
    4. NodeDataChanged
    5. NodeChildrenChanged
    • 其中后四種用於表示ZNode的狀態或者數據變更,而None則用於會話的狀態變更。
  6. EventType為None的Watch SessionWatch實例描述了,初始化一個ZooKeeper實例時注冊的Watcher接口
    1. 將在連接時收到EventType為None,KeeperState為SyncConnected,path為null的Event
    2. 將在失去連接時收到EventType為None,KeeperState為:Disconnected,path為null的Event
  7. ChildrenCallback 通過getChildren方法,可以設置ChildrenCallback,以便獲得獲得當子節點發生變化時的相關信息。
    • ChildrenCallback 的唯一接口:
    public void processResult(int rc, String path, Object ctx, List<String> children)
    
    • getChildren可以設置對應的Watcher,一旦發現節點的事件類型為NodeChildrenChanged后,可以繼續設置watch
  8. 例子:https://github.com/llohellohe/llohellohe.github.com/blob/master/readers/ZooKeeper/04-狀態變更.md
  9. 上述這篇例子我用zookeeper3.4.13運行會報NullPointException,我dubug后發現,該版本zookeeper在進入鏈接剛建立狀態(None)時,會添加默認defaultWatcher,而示例代碼初始化時,watch填了null,導致了后續,從set中得到的watcher為null,我認為這是個bug,初始化連接不放watch應該也是允許的。
 @Override
        public Set<Watcher> materialize(Watcher.Event.KeeperState state,
                                        Watcher.Event.EventType type,
                                        String clientPath)
        {
            Set<Watcher> result = new HashSet<Watcher>();

            switch (type) {
            case None://初始化連接時進入
                result.add(defaultWatcher);//defaultWatcher如果填空,后續會報錯
  private void processEvent(Object event) {
          try {
              if (event instanceof WatcherSetEventPair) {
                  // each watcher will process the event
                  WatcherSetEventPair pair = (WatcherSetEventPair) event;
                  for (Watcher watcher : pair.watchers) {
                      try {
                          watcher.process(pair.event);//watcher為null
  1. processEvent方法就是EvenetThread線程處理watcher監聽的地方

1.8.3. Curator

  1. Curator是構建在ZooKeeper上的API,它屏蔽一些復雜的ZooKeeper操作,並提供了一些擴展。

1.8.3.1. 流式API

  1. 一般的ZooKeeper創建節點的代碼如下:
zk.create("/mypath", new byte[0],
              ZooDefs.Ids.OPEN_ACL_UNSAFE,
              CreateMode.PERSISTENT);

1.8.3.2. 使用Curator后

zkc.create().withMode(CreateMode.PERSISTENT).forPath("/mypath", new byte[0]);

1.8.3.3. leader latch 和leader selection

  • 通過Curator可以方便的進行leader的選舉和控制

1.8.3.4. Leader Elections

  在ZK集群中,Leader的作用是保證變更操作(create\setData\delete)的順序性
它將接收到的請求轉換成事務,然后提議followers按照順序應用這些事務。在初始階段,所有的ZK服務端都處於LOOKING狀態,要么找到已經存在Leader結點,要么自己選舉出Leader。成為Leader的節點將進入LEADING狀態,其它則將進入FOLLOWING狀態

1.8.3.5. 選舉過程

  1. 進入LOOKING狀態的server將廣播消息,稱為vote。
  2. vote包含serverId和ZXID,比如(1,5)表示server id為1的服務端,它的ZXID為5。
  3. 每個server將比較自己的vote和收到的vote,如果:
    1. 收到vote的zxid大於自己的,則使用這個vote
    2. 如果zxid相等,則sid大的獲勝
    3. 當推選超過半數以上,則確定leader

1.9. 源碼分析

1.9.1. 客戶端組成

  • Zookeeper客戶端主要由如下核心部件構成。
    1. Zookeeper實例,客戶端入口
    2. ClientWatchManager, 客戶端Watcher管理器
    3. HostProvider,客戶端地址列表管理器。
    4. ClientCnxn,客戶端核心線程,內部包含了SendThread和EventThread兩個線程,SendThread為I/O線程,主要負責Zookeeper客戶端和服務器之間的網絡I/O通信;EventThread為事件線程,主要負責對服務端事件進行處理。
      1

1.9.2. 初始化

  • 客戶端在初始化和啟動過程中大體可以分為如下3個步驟
    1. 設置默認Watcher
    2. 設置Zookeeper服務器地址列表
    3. 創建ClientCnxn。
  • 若在Zookeeper構造方法中傳入Watcher對象時,那么Zookeeper就會將該Watcher對象保存在ZKWatcherManager的defaultWatcher中,並作為整個客戶端會話期間的默認Watcher。

1

1.9.3. 會話的創建

  1. 客戶端與服務端會話建立的整個過程,包括初始化階段(第一階段)、會話創建階段(第二階段)、響應處理階段(第三階段)三個階段。
    2
    細節:
    1

1.9.4. 服務器地址列表

  1. 在實例化Zookeeper時,用戶傳入Zookeeper服務器地址列表,如192.168.0.1:2181,192.168.0.2:2181,192.168.0.3:2181
    • Chroot:客戶端可以設置自己的命名空間,若客戶端設置了Chroot,此時,該客戶端對服務器的任何操作都將被限制在自己的命名空間下,如設置Choot為/app/X,那么該客戶端的所有節點路徑都是以/app/X為根節點。具體設置可以使輸入地址列表的時候加上192.168.0.1:2181/app/X
    • 地址列表管理:Zookeeper使用StaticHostProvider打散服務器地址(shuffle),並將服務器地址形成一個環形循環隊列,然后再依次取出服務器地址。

1.10. 總結

  1. zookeeper比較重要的概念就是選主算法,這里盡可能簡單的簡述一下,分兩類:

    • 全新集群選主:按照服務器啟動順序,判斷server_id大小,根據過半選舉的規則選主;比如服務器1-5對應server_id1-5,按順序啟動,每啟動一台會有個選主過程,服務器會交換選主信息,id大的勝出,啟動2台時,由於沒有超過半數以上的機器,所以繼續保持LOOKING,當第三台機器啟動,id最大,且選票結果超過半數,則確定leader為server_id=3的機器,后續4、5啟動,由於已經存在leader,只能當following
    • 非全新集群選主:這種情況說明zookeeper集群leader機器宕機,需要重新選舉,需要根據數據的票選輪數epoch、zxid和server_id判斷,先進行選舉信息的交換,票選輪數小的忽略,zxid大的勝出,zxid相同情況看server_id,server_id大的勝出
    • zookeeper3.4.13查看源碼可以發現,protected int electionAlg = 3;
    case 3:
            qcm = createCnxnManager();
            QuorumCnxManager.Listener listener = qcm.listener;
            if(listener != null){
                listener.start();
                le = new FastLeaderElection(this, qcm);
            } else {
                LOG.error("Null listener when initializing cnx manager");
            }
            break;
    

參考

https://blog.csdn.net/feixiang2039/article/details/79810102#zookeeper-命令 zookeeper 客戶端 zkCli 命令詳解

https://github.com/llohellohe/llohellohe.github.com/blob/master/readers/ZooKeeper llohellohe/llohellohe.github.com

http://www.cnblogs.com/leesf456/p/6098255.html Zookeeper客戶端

https://blog.csdn.net/cxhzqhzq/article/details/6568040 Zookeeper全解析——Paxos作為靈魂

http://blog.chinaunix.net/uid-26726125-id-4038581.html zookeeper跟經典paxos的對比(附源碼)

https://blog.csdn.net/panxj856856/article/details/80403487 FastLeaderElection選舉算法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM