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包含DisconnectedSyncConnectedAuthFailedConnectedReadOnlySaslAuthenticatedExpired等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的作用是保证变更操作(createsetDatadelete)的顺序性。
它将接收到的请求转换成事务,然后提议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选举算法