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-%E7%8A%B6%E6%80%81%E5%8F%98%E6%9B%B4.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.9.2. 初始化

  • 客户端在初始化和启动过程中大体可以分为如下3个步骤
    1. 设置默认Watcher
    2. 设置Zookeeper服务器地址列表
    3. 创建ClientCnxn。
  • 若在Zookeeper构造方法中传入Watcher对象时,那么Zookeeper就会将该Watcher对象保存在ZKWatcherManager的defaultWatcher中,并作为整个客户端会话期间的默认Watcher。

1.9.3. 会话的创建

  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-%E5%91%BD%E4%BB%A4 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选举算法

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券