1. zookeeper使用詳解(命令、客戶端、源碼)
1.1. 前言
zookeeper我們常用來做分布式協調中間件,很多時候我們都接觸不到它的原理和用法,我對他的了解也僅限於知道它可以做分布式協調、配置管理、分布式鎖,並且有個watch節點監聽常常能聽到。接下來我要系統的學下zookeeper的功能和原理,一起走進zookeeper的世界
1.2. 概述
zookeeper主要目的就是為了分布式應用提供協同服務,zookeeper的節點管理機制,當節點發生變化時(創建、刪除、數據變更),可以通知各個客戶端,利用這種特性,zk的主要場景就如我前面說的:
1. 統一配置:把配置放在ZooKeeper的節點中維護,當配置變更時,客戶端可以收到變更的通知,並應用最新的配置。
2. 集群管理:集群中的節點,創建ephemeral的節點,一旦斷開連接,ephemeral的節點會消失,其它的集群機器可以收到消息。
3. 分布式鎖:多個客戶端發起節點創建操作,只有一個客戶端創建成功,從而獲得鎖。
1.3. zk基本操作
- ZNode ZNode是ZK樹形結構的一個節點,它可以包含或者不包含數據。
- ZK提供了如下API,用於操作ZNode。
- create path data
- delete path data
- exists path
- getdata path
- putdata path data
- getChildren path
- ZK客戶端通過建立一個Session會話,來連接ZK服務,通過這些API來操作ZNode。
1.4. zk模式
- ZNode模式 目前ZNode包含持久模式和短暫模式ephemeral。
- ephemeral模式指的是這個節點在session斷了之后就會消失。
- 持久模式和ephemeral模式外,ZNode還可以是有序的(自動分配自增ID到節點上,比如task-1,task-2,task-3)
- 因此ZNode一共有四種形態:
- 持久
- 持久有序
- ephemeral
- ephemeral有序
1.5. watch機制
- Watch和Notifications Watch可以避免每次主動去請求數據是否變化,而是當ZNode變化時,來通知。
- Watch是個一次性操作,每次收到通知后,必須重新watch,如果時間比較久或者ZNode更新頻繁,在此時間之間,可能會有更新沒有被通知到(還沒來得急watch)。
- ZNode的創建、刪除和修改都可以被watch到。
1.6. FAQ
1.6.1. 客戶端對服務器列表的輪詢機制
- 客戶端建立與zk會話的時候需要我們填寫zk服務器列表,之后該列表會被隨機打散然后每次請求輪詢該列表,這種打散是一次性的,之后每次都是這個順序
- 知道客戶端輪詢原理,可以知道列表是可以重復填寫的,這樣也可以通過重復填寫配置地址來增加權重,但也會有風險,可能使server切換耗時過長,倒是session_expired
1.6.2. 客戶端常遇到的浴場Connectionloss(連接斷開)和Sessionexpired(session過期)處理
- 在ZooKeeper中,服務器和客戶端之間維持的是一個長連接,在 SESSION_TIMEOUT 時間內,服務器會確定客戶端是否正常連接(客戶端會定時向服務器發送heart_beat),服務器重置下次SESSION_TIMEOUT時間。因此,在正常情況下,Session一直有效,並且zk集群所有機器上都保存這個Session信息。在出現問題情況下,客戶端與服務器之間連接斷了(客戶端所連接的那台zk機器掛了,或是其它原因的網絡閃斷),這個時候客戶端會主動在地址列表(初始化的時候傳入構造方法的那個參數connectString)中選擇新的地址進行連接。
- connectionloss通常發生在連接的zk掛了,這個時候只要等待客戶端連接上新的zk機器(zk必須集群),然后確認操作是否執行成功
- sessionexpired通常發生在zk客戶端和服務器的連接斷了,視圖連上新的zk機器,如果這個過程耗時過長,超過session_timeout時間,那么服務器認為這個session已經結束(服務器無法確認時因為其他異常原因還是客戶端主動結束會話),開始清除和這個會話相關的信息,包括會話創建的臨時節點和注冊的watcher。這時客戶端重新連接上服務器,服務器會報sessionexpired。這個時候解決辦法要看業務情況了,只能重新實例化zk對象,重新操作節點數據
1.6.3. 創建的臨時節點什么時候會被刪除,是連接一斷就刪除嗎?
- 連接斷了之后,ZK不會馬上移除臨時數據,只有當SESSIONEXPIRED之后,才會把這個會話建立的臨時數據移除。因此,用戶需要謹慎設置Session_TimeOut
1.6.4. zk日志清理
- zk不會自動清理日志,參考:https://blog.51cto.com/nileader/932156
1.7. zkClient命令行(包含了全部命令)
1.7.1. 創建節點
-
語法
create [-s] [-e] path data acl
-
-s 創建有序節點 -e創建臨時節點
-
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
-
上面的命令可以看出
- 第一不能直接創建多級幾點
- 第二創建臨時節點不能有子節點
- 第三有序節點節點名后會加上序號
1.7.2. 列出節點 ls
ls path [watch]
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
[]
- 可以看出,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.7.3. 獲取節點信息 get
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
- 可以看到,get到路徑有詳細信息,和ls2獲得的信息一樣
- 每一個對znode樹的更新操作,都會被賦予一個全局唯一的ID,我們稱之為zxid(ZooKeeper Transaction ID)
- 更新操作的ID按照發生的時間順序升序排序。例如,z1大於z2,那么z1的操作就早於z2操作。
- 每個 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
stat path [watch]
- 與 get 的區別是,不會列出 znode 的值。
1.7.5. 修改節點 set
set path data [version]
- 修改已經存在的節點的值
[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
- 可以看到,在修改節點值之后,mZxid、mtime、dataVersion 都發生了變化。
1.7.6. 刪除節點 rmr
rmr path
[zk: localhost:2181(CONNECTED) 12] rmr /temp2
[zk: localhost:2181(CONNECTED) 13] get /temp2
Node does not exist: /temp2
- 刪除 /mynode,不會返回任何內容。如果有子節點的時候,連帶子節點也一起刪除。
1.7.7. 刪除節點 delete
delete path [version]
- 調用delete和set操作時,如果指定znode版本號,需要與當前的版本號匹配。如果版本號不匹配,操作將會失敗。失敗的原因可能是在我們提交之前,該znode已經被修改過了,版本號發生了增量變化。如果不指定版本號,就是直接操作最新版本的 znode。
- 如果要刪除的節點有子節點,不能刪除
[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
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
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)
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
- 可以看到設置off后,監聽打印輸出就沒有了
1.7.11. 關閉連接 close
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
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]
- 可以看到默認連接的是本地的2181
1.7.13. 退出連接 quit
quit
直接退出當前的 zkCli 命令行。
1.7.14. 強制同步 sync
sync path
- sync方法會強制客戶端所連接的服務器狀態與leader的狀態同步,這樣在讀取 path 的值就是最新的值了
1.7.15. ACL 操作
- 一個znode中不僅包含了存儲的數據,還有 ACL(Access Control List)。znode的創建時,可以給它設置一個ACL(Access Control List),來決定誰可以對znode做哪些操作。
- 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
- ZooKeeper內置了一些權限控制方案,可以用以下方案為每個節點設置權限:
方案 | 描述 |
---|---|
world | 只有一個用戶:anyone,代表所有人(默認) |
ip | 使用IP地址認證 |
auth | 使用已添加認證的用戶認證 |
digest | 使用“用戶名:密碼”方式認證 |
- 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 方案
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
- 可以看出,在修改權限為 cdr 之后,不能再設置節點數據了。注意 aclVersion 也發生了變化。
1.7.18.2. IP 方案
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 方案
setAcl <path> digest:<user>:<password>:<acl>
- 這里的密碼是經過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.7.19. zookeeper quota
- zookeeper quota 機制支持節點個數(namespace)和空間大小(bytes)的設置。
- zookeeper quota 保存在 /zookeeper/quota 節點下,可以設置該節點的 ACL 權限,以防其他人修改。
- 語法
listquota path
、setquota -n|-b val path
、delquota [-n|-b] path
- -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
- 可以看到,超出節點數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
- 刪除節點quota,count變成-1
1.8. java操作
1.8.1. 異步操作
- 主流異步操作
1.8.2. 狀態變更
- Watcher優勢 通過watcher,可以避免主動輪詢導致的額外負擔,更加實時和有效率。
- Watcher接口,僅有一個實現接口
public void process(WatchedEvent event)
- WatchedEvent代表watcher到的事件,它包含發生了什么事件,ZooKeeper的當前連接狀態以及產生事件的ZNode路徑。
- KeeperState
- EventType
- path
- KeeperState包含Disconnected\SyncConnected\AuthFailed\ConnectedReadOnly\SaslAuthenticated\Expired等6種狀態。
- EventType包含五種狀態:
- None
- NodeCreated
- NodeDeleted
- NodeDataChanged
- NodeChildrenChanged
- 其中后四種用於表示ZNode的狀態或者數據變更,而None則用於會話的狀態變更。
- EventType為None的Watch SessionWatch實例描述了,初始化一個ZooKeeper實例時注冊的Watcher接口
- 將在連接時收到EventType為None,KeeperState為SyncConnected,path為null的Event
- 將在失去連接時收到EventType為None,KeeperState為:Disconnected,path為null的Event
- ChildrenCallback 通過getChildren方法,可以設置ChildrenCallback,以便獲得獲得當子節點發生變化時的相關信息。
- ChildrenCallback 的唯一接口:
public void processResult(int rc, String path, Object ctx, List<String> children)
- getChildren可以設置對應的Watcher,一旦發現節點的事件類型為NodeChildrenChanged后,可以繼續設置watch
- 例子:https://github.com/llohellohe/llohellohe.github.com/blob/master/readers/ZooKeeper/04-狀態變更.md
- 上述這篇例子我用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
- processEvent方法就是EvenetThread線程處理watcher監聽的地方
1.8.3. Curator
- Curator是構建在ZooKeeper上的API,它屏蔽一些復雜的ZooKeeper操作,並提供了一些擴展。
1.8.3.1. 流式API
- 一般的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. 選舉過程
- 進入LOOKING狀態的server將廣播消息,稱為vote。
- vote包含serverId和ZXID,比如(1,5)表示server id為1的服務端,它的ZXID為5。
- 每個server將比較自己的vote和收到的vote,如果:
- 收到vote的zxid大於自己的,則使用這個vote
- 如果zxid相等,則sid大的獲勝
- 當推選超過半數以上,則確定leader
1.9. 源碼分析
1.9.1. 客戶端組成
- Zookeeper客戶端主要由如下核心部件構成。
- Zookeeper實例,客戶端入口。
- ClientWatchManager, 客戶端Watcher管理器。
- HostProvider,客戶端地址列表管理器。
- ClientCnxn,客戶端核心線程,內部包含了SendThread和EventThread兩個線程,SendThread為I/O線程,主要負責Zookeeper客戶端和服務器之間的網絡I/O通信;EventThread為事件線程,主要負責對服務端事件進行處理。
1.9.2. 初始化
- 客戶端在初始化和啟動過程中大體可以分為如下3個步驟
- 設置默認Watcher
- 設置Zookeeper服務器地址列表
- 創建ClientCnxn。
- 若在Zookeeper構造方法中傳入Watcher對象時,那么Zookeeper就會將該Watcher對象保存在ZKWatcherManager的defaultWatcher中,並作為整個客戶端會話期間的默認Watcher。
1.9.3. 會話的創建
- 客戶端與服務端會話建立的整個過程,包括初始化階段(第一階段)、會話創建階段(第二階段)、響應處理階段(第三階段)三個階段。
細節:
1.9.4. 服務器地址列表
- 在實例化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),並將服務器地址形成一個環形循環隊列,然后再依次取出服務器地址。
- Chroot:客戶端可以設置自己的命名空間,若客戶端設置了Chroot,此時,該客戶端對服務器的任何操作都將被限制在自己的命名空間下,如設置Choot為/app/X,那么該客戶端的所有節點路徑都是以/app/X為根節點。具體設置可以使輸入地址列表的時候加上
1.10. 總結
-
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;
- 也就是說zk默認使用的是FastLeaderElection選舉算法,參考FastLeaderElection選舉算法
參考
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選舉算法