zookeeper使用
安装与配置
使用安装包安装
# 下载
$ wget https://downloads.apache.org/zookeeper/zookeeper-3.6.0/apache-zookeeper-3.6.0-bin.tar.gz
$ tar -zxvf apache-zookeeper-3.6.0-bin.tar.gz
$ cd apache-zookeeper-3.6.0-bin
# 配置
$ cp conf/zoo_sample.cfg conf/zoo.cfg
# 编辑配置文件,下面介绍
$ vi conf/zoo.cfg
# 启动服务端
$ bin/zkServer.sh start
# 查看状态
$ bin/zkServer.sh status
# 停止
$ bin/zkServer.sh stop
# 重启
$ bin/zkServer.sh restart
# 用 jps 查看状态
$ jps
46193 QuorumPeerMain
Homebrew 安装 zookeeper
$ brew install zookeeper
# 启动服务
$ brew services start zookeeper
# 停止服务
$ brew services stop zookeeper
# 配置文件位置
$ ls /usr/local/etc/zookeeper
defaults log4j.properties zoo.cfg zoo_sample.cfg
配置文件
下面是配置文件的内容:
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/tmp/zookeeper/data
dataLogDir=/tmp/zookeeper/log
# the port at which the clients will connect
clientPort=2181
tickTime:配置单元时间。单元时间是ZooKeeper的时间计算单元,其他的时间间隔都是使用tickTime的倍数来表示的。
initLimit:节点的初始化时间。该参数用于Follower(从节点)的启动,并完成与Leader(主节点)进行数据同步的时间。Follower节点在启动过程中,会与Leader节点建立连接并完成对数据的同步,从而确定自己的起始状态。Leader节点允许Follower节点在initLimit时间内完成这项工作。该参数默认值为10,表示是参数tickTime值的10倍。
syncLimit:心跳最大延迟周期。该参数用于配置Leader节点和Follower节点之间进行心跳检测的最大延时时间。在ZK集群运行的过程中,Leader节点会通过心跳检测来确定Follower节点是否存活。如果Leader节点在syncLimit时间内无法获取到Follower节点的心跳检测响应,那么Leader节点就会认为该Follower节点已经脱离了和自己的同步。该参数默认值为5,表示是参数tickTime值的5倍。
dataDir:是zookeeper持久化数据存放的目录。myid文件处于此目录下。
dataLogDir:日志目录选项,如果没有设置该参数,默认将使用和dataDir相同的设置。
clientPort:zookeeper监听客户端连接的端口,默认是2181。
zookeeper cli
使用 brew 安装,已经把 zookeeper bin 目录下的命令添加的系统中,因此在终端直接执行 zkCli,就创建了一个 zk 客户端,连接 zk 服务。
使用压缩包安装,可以执行:
$ bin/zkCli.sh -server 127.0.0.1:2181
输入 help 命令(其实输入任何 zkCli 不能识别的命令,都会列出所有的命令),查看可用的命令:
对 znode 进行增删改查
创建节点 create
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
-s 创建有序节点
如果在创建znode时,我们使用排序标志的话,ZooKeeper会在我们指定的 znode 名字后面增加一个数字。我们继续加入相同名字的znode时,这个数字会不断增加。这个序号的计数器是由这些排序znode的父节点来维护的。
-e 创建临时节点
znode有两种类型:ephemeral 和 persistent。在创建znode时,我们指定znode的类型,并且在之后不会再被修改。当创建znode的客户端的session结束后,ephemeral类型的znode将被删除。persistent类型的znode在创建以后,就与客户端没什么联系了,除非主动去删除它,否则他会一直存在。Ephemeral znode没有任何子节点。
acl 在下面的《 ACL 操作》中详细介绍。
使用方法:
普通节点
[zk: localhost:2181(CONNECTED) 3] create /mynode hello
Created /mynode
[zk: localhost:2181(CONNECTED) 4] create /mynode/subnode world
Created /mynode/subnode
[zk: localhost:2181(CONNECTED) 9] get /mynode
hello
[zk: localhost:2181(CONNECTED) 10] get /mynode/subnode
world
有序节点
[zk: localhost:2181(CONNECTED) 4] create -s /mynode hello
Created /mynode0000000004
[zk: localhost:2181(CONNECTED) 6] create -s /mynode world
Created /mynode0000000005
临时节点
[zk: localhost:2181(CONNECTED) 7] create -e /temp hello
Created /temp
退出 zkCli,然后再重新打开它,/temp 节点已经被删除了。
列出节点 ls
ls [-s] [-w] [-R] path
-w 添加一个 watch(监视器),如果该节点发生变化,watch 可以使客户端得到通知。watch 只能被触发一次。如果要一直获得 znode 的创建和删除的通知,那么就需要不断的在znode上开启观察模式。如果在该 path 下节点发生变化,会产生 NodeChildrenChanged 事件,删除节点,会产生 NodeDeleted 事件。
使用方法:
[zk: localhost:2181(CONNECTED) 12] ls /
[mynode, mynode0000000003, mynode0000000004, test, zookeeper]
[zk: localhost:2181(CONNECTED) 13] ls -s /
[mynode, mynode0000000003, mynode0000000004, test, zookeeper]
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x300000053
cversion = 7
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 5
[zk: localhost:2181(CONNECTED) 2] ls /mynode
[subnode]
使用 -w 查看 /mynode 节点,然后在它下面添加(删除)子节点,就会触发该 watch。在其他节点下创建子节点,不会触发该 watch。
[zk: localhost:2181(CONNECTED) 20] ls -w /mynode
[subnode]
[zk: localhost:2181(CONNECTED) 21] create /mynode/subnode2
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/mynode
Created /mynode/subnode2
# 监听父节点,删除子节点,产生 NodeChildrenChanged事件
[zk: localhost:2181(CONNECTED) 22] ls -w /mynode
[subnode, subnode2]
[zk: localhost:2181(CONNECTED) 23] delete /mynode/subnode2
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/mynode
# 监听子节点,删除子节点,产生 NodeDeleted 事件
[zk: localhost:2181(CONNECTED) 51] create /mynode/subnode2
Created /mynode/subnode2
[zk: localhost:2181(CONNECTED) 52] ls -w /mynode/subnode2
[]
[zk: localhost:2181(CONNECTED) 53] delete /mynode/subnode2
WATCHER::
WatchedEvent state:SyncConnected type:NodeDeleted path:/mynode/subnode2
从上面的操作可以看到,在 /mynode 下添加了 subnode2 节点之后,触发了 watch,WatchedEvent 的类型是 NodeChildrenChanged。之后再删除 subnode2 节点,也出发了 watch。
获取节点信息 get
get [-s] [-w] path
-w 添加一个 watch(监视器),如果节点内容发生改变,会产生 NodeDataChanged 事件;如果删除节点,会产生 NodeDeleted 事件。
使用方法
[zk: localhost:2181(CONNECTED) 20] ls -w /mynode
[subnode]
[zk: localhost:2181(CONNECTED) 21] create /mynode/subnode2
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/mynode
Created /mynode/subnode2
# 监听父节点,删除子节点,产生 NodeChildrenChanged事件
[zk: localhost:2181(CONNECTED) 22] ls -w /mynode
[subnode, subnode2]
[zk: localhost:2181(CONNECTED) 23] delete /mynode/subnode2
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/mynode
# 监听子节点,删除子节点,产生 NodeDeleted 事件
[zk: localhost:2181(CONNECTED) 51] create /mynode/subnode2
Created /mynode/subnode2
[zk: localhost:2181(CONNECTED) 52] ls -w /mynode/subnode2
[]
[zk: localhost:2181(CONNECTED) 53] delete /mynode/subnode2
WATCHER::
WatchedEvent state:SyncConnected type:NodeDeleted path:/mynode/subnode2
每一个对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,该节点子节点的个数
检查状态 stat
stat [-w] path
-w 添加一个 watch(监视器),如果节点内容发生改变,会产生 NodeDataChanged 事件;如果删除节点,会产生 NodeDeleted 事件。
与 get 的区别是,不回列出 znode 的值。
使用方法
[zk: localhost:2181(CONNECTED) 56] stat /mynode
cZxid = 0x30000004c
ctime = Sun Apr 05 15:48:14 CST 2020
mZxid = 0x30000005e
mtime = Sun Apr 05 16:09:32 CST 2020
pZxid = 0x300000067
cversion = 16
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
修改节点 set
set [-s] [-v version] path data
修改已经存在的节点的值
使用方法
[zk: localhost:2181(CONNECTED) 57] set /mynode hello
[zk: localhost:2181(CONNECTED) 58] ls /mynode
[]
[zk: localhost:2181(CONNECTED) 59] stat /mynode
cZxid = 0x30000004c
ctime = Sun Apr 05 15:48:14 CST 2020
mZxid = 0x300000068
mtime = Sun Apr 05 16:20:34 CST 2020
pZxid = 0x300000067
cversion = 16
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
可以看到,在修改节点值之后,mZxid、mtime、dataVersion 都发生了变化。
删除节点 deleteall
deleteall path [-b batch size]
使用方法
[zk: localhost:2181(CONNECTED) 34] delete /mynode
删除 /mynode,不会返回任何内容。如果有子节点的时候,都会删除。
删除节点 delete
delete [-v version] path
调用delete和set操作时,如果指定znode版本号,需要与当前的版本号匹配。如果版本号不匹配,操作将会失败。失败的原因可能是在我们提交之前,该znode已经被修改过了,版本号发生了增量变化。如果不指定版本号,就是直接操作最新版本的 znode。
使用方法
[zk: localhost:2181(CONNECTED) 15] create /mynode hello
Created /mynode
[zk: localhost:2181(CONNECTED) 16] delete /mynode
如果要删除的节点有子节点,不能删除
[zk: localhost:2181(CONNECTED) 33] create /mynode/sub sub
Created /mynode/sub
[zk: localhost:2181(CONNECTED) 34] delete /mynode
Node not empty: /mynode
其他指令
历史记录 history
history 列出最近的10条历史记录
[zk: localhost:2181(CONNECTED) 7] history
0 - history
1 - create /mynode hello
2 - ls /
3 - set /mynode worold
4 - get /mynode
5 - stat /mynode
6 - rmr /mynode
7 - history
重复之前的命令 redo
redo cmdno 根据 cmdno 重复之前的命令,cmdno 就是方括号里面最后的数字,每次执行命令都会自增。
[zk: localhost:2181(CONNECTED) 5] create /mynode hello
Created /mynode
[zk: localhost:2181(CONNECTED) 6] rmr /mynode
[zk: localhost:2181(CONNECTED) 7] redo 5
Created /mynode
是否输出 watch 事件(printwatches)
语法
printwatches on|off
使用方法
[zk: localhost:2181(CONNECTED) 43] printwatches
printwatches is on
[zk: localhost:2181(CONNECTED) 44] ls /mynode 1
[sub]
[zk: localhost:2181(CONNECTED) 45] create /mynode/child child
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/mynode
Created /mynode/child
如果设置 printwatches off ,就看不到上面的 WATCHER 事件了。
关闭连接 close
close
[zk: localhost:2181(CONNECTED) 50] close
[zk: localhost:2181(CLOSED) 51]
[zk: localhost:2181(CLOSED) 52] ls /
Not connected
打开连接 connect
connect host:port
[zk: localhost:2181(CLOSED) 52] connect
[zk: localhost:2181(CONNECTING) 53]
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 53]
指定 host:port 可以连接远程的 zk 服务。缺省的时候,会连接本地的 2181 端口。
退出连接 quit
quit
直接退出当前的 zkCli 命令行。
强制同步 sync
sync path
sync方法会强制客户端所连接的服务器状态与leader的状态同步,这样在读取 path 的值就是最新的值了。
ACL 操作
一个znode中不仅包含了存储的数据,还有 ACL(Access Control List)。znode的创建时,可以给它设置一个ACL(Access Control List),来决定谁可以对znode做哪些操作。
ACL 具有以下特点:
-
ZooKeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限
-
每个znode支持设置多种权限控制方案和多个权限
-
子节点不会继承父节点的权限,客户端无权访问某节点,但可能可以访问它的子节点
ACL Permissions
ACL 权限 | ACL 简写 | 允许的操作 |
---|---|---|
CREATE | c | 创建子节点 |
READ | r | 获取节点的数据和它的子节点 |
WRITE | w | 设置节点的数据 |
DELETE | d | 删除子节点 (仅下一级节点) |
ADMIN | a | 设置 ACL 权限 |
权限相关命令
命令 | 语法 | 描述 |
---|---|---|
getAcl | getAcl [-s] path | 读取ACL权限 |
setAcl | setAcl [-s] [-v version] [-R] path acl | 设置ACL权限 |
addauth | addauth scheme auth | 添加认证用户 |
create | create [-s] [-e] path data acl | 创建节点时指明 ACL 权限 |
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
world 方案
设置方式:setAcl <path> world:anyone:<acl>
默认情况下时 world 方法,任何人有所有权限:
[zk: localhost:2181(CONNECTED) 6] getAcl /mynode
'world,'anyone
: cdrwa
[zk: localhost:2181(CONNECTED) 7] setAcl /mynode world:anyone:cdr
cZxid = 0x54a
ctime = Tue Apr 03 09:26:36 CST 2018
mZxid = 0x54a
mtime = Tue Apr 03 09:26:36 CST 2018
pZxid = 0x54a
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 15] set /mynode hello
Authentication is not valid : /mynode
可以看出,在修改权限为 cdr 之后,不能再设置节点数据了。注意 aclVersion 也发生了变化。
IP 方案
设置方式:setAcl <path> ip:<ip>:<acl>
<ip>:可以是具体IP也可以是IP/bit格式,即IP转换为二进制,匹配前bit位,如192.168.0.0/16匹配192.168.*.*
[zk: localhost:2181(CONNECTED) 19] create /mynode hello
Created /mynode
[zk: localhost:2181(CONNECTED) 20] setAcl /mynode ip:192.168.1.250:cdrwa
cZxid = 0x552
ctime = Tue Apr 03 09:38:58 CST 2018
mZxid = 0x552
mtime = Tue Apr 03 09:38:58 CST 2018
pZxid = 0x552
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 21] getAcl /mynode
'ip,'192.168.1.250
: cdrwa
使用其他电脑方法方法该节点:
#使用IP非 192.168.100.1 的机器
[zk: localhost:2181(CONNECTED) 0] get /node2
Authentication is not valid : /node2 #没有权限
[zk: localhost:2181(CONNECTED) 1] delete /node2 #删除成功(因为设置DELETE权限仅对下一级子节点有效,并不包含此节点)
auth 方案
设置方式
addauth digest <user>:<password> #添加认证用户
setAcl <path> auth:<user>:<acl>
示例:
[zk: localhost:2181(CONNECTED) 22] create /mynode1 hello
Created /mynode1
[zk: localhost:2181(CONNECTED) 23] addauth digest admin:admin #添加认证用户
[zk: localhost:2181(CONNECTED) 24] setAcl /mynode1 auth:admin:cdrwa
cZxid = 0x554
ctime = Tue Apr 03 09:44:32 CST 2018
mZxid = 0x554
mtime = Tue Apr 03 09:44:32 CST 2018
pZxid = 0x554
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 25] getAcl /mynode1
'digest,'admin:x1nq8J5GOJVPY6zgzhtTtA9izLc=
: cdrwa
[zk: localhost:2181(CONNECTED) 26] get /mynode1
hello #刚才已经添加认证用户,可以直接读取数据,断开会话重连需要重新addauth添加认证用户
cZxid = 0x554
ctime = Tue Apr 03 09:44:32 CST 2018
mZxid = 0x554
mtime = Tue Apr 03 09:44:32 CST 2018
pZxid = 0x554
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
digest 方案
设置方式
setAcl <path> digest:<user>:<password>:<acl>
这里的密码是经过SHA1及BASE64处理的密文,在SHELL中可以通过以下命令计算:
echo -n <user>:<password> | openssl dgst -binary -sha1 | openssl base64
先来算一个密文密码:
echo -n admin:admin | openssl dgst -binary -sha1 | openssl base64
x1nq8J5GOJVPY6zgzhtTtA9izLc=
示例:
[zk: localhost:2181(CONNECTED) 8] create /mynode2 hello
Created /mynode2
#使用是上面算好的密文密码添加权限:
[zk: localhost:2181(CONNECTED) 9] setAcl /mynode2
digest:admin:x1nq8J5GOJVPY6zgzhtTtA9izLc=:cdrwa
cZxid = 0x55a
ctime = Tue Apr 03 13:17:12 CST 2018
mZxid = 0x55a
mtime = Tue Apr 03 13:17:12 CST 2018
pZxid = 0x55a
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 10] getAcl /mynode2
'digest,'admin:x1nq8J5GOJVPY6zgzhtTtA9izLc=
: cdrwa
[zk: localhost:2181(CONNECTED) 11] get /mynode2
#没有权限
Authentication is not valid : /mynode2
[zk: localhost:2181(CONNECTED) 12] addauth digest admin:admin #添加认证用户
[zk: localhost:2181(CONNECTED) 13] get /mynode2
hello #成功读取数据
cZxid = 0x55a
ctime = Tue Apr 03 13:17:12 CST 2018
mZxid = 0x55a
mtime = Tue Apr 03 13:17:12 CST 2018
pZxid = 0x55a
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
创建节点时指定 ACL
#添加 admin 用户
[zk: localhost:2181(CONNECTED) 2] addauth digest admin:admin
#创建节点时赋予权限
[zk: localhost:2181(CONNECTED) 3] create /mynode hello auth:admin:cdrwa
Created /mynode
[zk: localhost:2181(CONNECTED) 4] getAcl /mynode
'digest,'admin:x1nq8J5GOJVPY6zgzhtTtA9izLc=
: cdrwa
[zk: localhost:2181(CONNECTED) 5] close
[zk: localhost:2181(CLOSED) 6] connect
[zk: localhost:2181(CONNECTING) 7]
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
#断开会话重连需要重新addauth添加认证用户
[zk: localhost:2181(CONNECTED) 7] get /mynode
Authentication is not valid : /mynode
[zk: localhost:2181(CONNECTED) 8] addauth digest admin:admin
[zk: localhost:2181(CONNECTED) 9] get /mynode
hello
cZxid = 0x56c
ctime = Tue Apr 03 15:00:27 CST 2018
mZxid = 0x56c
mtime = Tue Apr 03 15:00:27 CST 2018
pZxid = 0x56c
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
注意了!使用 rmr 删除节点没有权限时,竟然可以使用 delete
[zk: localhost:2181(CONNECTED) 25] rmr /mynode
Authentication is not valid : /mynode
[zk: localhost:2181(CONNECTED) 26] delete /mynode
zookeeper quota
zookeeper quota 机制支持节点个数(namespace)和空间大小(bytes)的设置。
zookeeper quota 保存在 /zookeeper/quota 节点下,可以设置该节点的 ACL 权限,以防其他人修改。
语法:
listquota path
setquota -n|-b val path
delquota [-n|-b] path
使用方法:
# 目前还没有任何设置
[zk: localhost:2181(CONNECTED) 9] ls /zookeeper/quota
[]
# 还没有为 /mynode 设置 quota
[zk: localhost:2181(CONNECTED) 10] listquota /mynode
absolute path is /zookeeper/quota/mynode/zookeeper_limits
quota for /mynode does not exist.
# -n表示设置znode count限制,这里表示/mynode这个path下的znode count个数限制为3(包括/mynode本身)
[zk: localhost:2181(CONNECTED) 11] setquota -n 3 /mynode
Comment: the parts are option -n val 3 path /mynode
[zk: localhost:2181(CONNECTED) 12] create /mynode/sub1 hello
Created /mynode/sub1
[zk: localhost:2181(CONNECTED) 9] listquota /mynode
absolute path is /zookeeper/quota/mynode/zookeeper_limits
Output quota for /mynode count=3,bytes=-1
Output stat for /mynode count=2,bytes=6
注意,即使节点数超出了限制,也不会看到提示信息,zookeeper 只会在日志中提醒一下。
使用 listquota 列出了节点的设置的 quota,和节点实际的容量。
[zk: localhost:2181(CONNECTED) 20] delquota -n /mynode
[zk: localhost:2181(CONNECTED) 21] listquota /mynode
absolute path is /zookeeper/quota/mynode/zookeeper_limits
Output quota for /mynode count=-1,bytes=-1
Output stat for /mynode count=2,bytes=6
删除 quota 之后,count 也变成了 -1
至此,Zookeeper客户端所有的命令介绍完毕!