转载自:分布式服务管理框架-Zookeeper客户端zkCli.sh使用详解
在学习zookeeper(下面简称zk)客户端之前,有必要先了解一下zk的数据模型。zk维护着一个逻辑上的树形层次结构,树中的节点称为znode,和Linux系统的文件系统结构非常相似,如下图所示:
这种数据结构有如下特点:
- 每个znode都有唯一路径标识,最顶层的znode为/,比如p_2这个znode的路径标识为/app1/p_2,znode只支持绝对路径,不支持相对路径,也不支持“.”和“..”
- znode可以有子节点,并且每个znode可以存储数据。但zk是被设计用来协调管理服务的,因此znode里存储的都是一些小数据,而不是大容量的数据,数据容量一般在1M范围内。
- znode的数据有版本号,可以用在并发访问场景中,用乐观锁机制实现数据的一致性
- znode分为临时节点和永久节点,zk的客户端和服务器通信采用长连接的方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为session,如果znode是临时节点,当session失效(即客户端与服务器断开连接),znode会被服务器自动删除。
- znode的节点名称可以自动编号,如果app1已经存在,再创建的话,将会自动命名为app2,这种节点称为序列节点。
- znode可以被监控,包括这个节点中存储的数据被修改、子节点列表变化(删除或新增子节点)等,一旦变化,zk服务器会通过所有监控该节点的客户端,这是zk的核心特性,zk很多的功能都是基于这个特性实现的。
zkCli.sh脚本是Zookeeper安装包中自带的一个客户端,放在$ZK_HOME/bin目录下,本文ZK安装在/opt/zookeeper-3.4.9。
zkCli.sh客户端连接到ZK服务器的语法为:zkCli.sh -timeout 5000 -r -server ip:port
连接参数解释:
- -timeout:表示客户端向zk服务器发送心跳的时间间隔,单位为毫秒。因为zk客户端与服务器的连接状态是通过心跳检测来维护的,如果在指定的时间间隔内,zk客户端没有向服务器发送心跳包,服务器则会断开与该客户端的连接。参数5000,表示zk客户端向服务器发送心跳的间隔为5秒。
- -r:表示客户端以只读模式连接
- -server:指定zk服务器的IP与端口,zk默认的客户端端口为2181
shell> cd /usr/local/zookeeper/bin
shell> ./zkCli.sh -timeout 5000 -server 127.0.0.1:2181
当然如果在Windows环境下,可以执行运行zkCli.cmd就能直接连接本地已启动的zookeeper服务器
若出现上图提示所示,表示已经成功连接到服务器。
在客户端交互命令行中,输入h查询可以使用的客户端命令:
[zk: 127.0.0.1:2181(CONNECTED) 0] h
ZooKeeper -server host:port cmd args
stat path [watch]
set path data [version] ls path [watch] delquota [-n|-b] path ls2 path [watch] setAcl path acl setquota -n|-b val path history redo cmdno printwatches on|off delete path [version] sync path listquota path rmr path get path [watch] create [-s] [-e] path data acl addauth scheme auth quit getAcl path close connect host:port
这些命令的作用和关系型数据库的SQL语句类似,zk的命令是对节点和数据进行增删改查操作,而SQL则是对表的数据增册改查操作。下面详细介绍所有命令的使用方法:
1、查询子节点列表
语法:ls path
path:节点路径
shell> ls /
[zookeeper]
目前根节点下只有zookeeper一个节点,是zk默认创建的,用于存储节点的一些状态信息,比如节点配额。
2、创建节点
语法:create path [-s] [-e] data acl
path:节点路径
-s:指定该节点是一个序列节点,创建同名的节点时,会给节点自动加上编号
-e:指定该节点是一个临时节点,默认是永久节点。临时节点会在客户端与服务器断开连接时,zk会将其创建的所有临时节点全部删除
data:存储在节点中的数据
acl:设置子节点访问权限,默认所有人都可以对该节点进行读写操作
# 1> 在根目录创建了一个`node_01`的节点,指定的数据为mydata
shell> create /node_01 mydata
Created /node_01
shell> ls /
[node_01, zookeeper]
# 2> 创建一个临时节点(创建之后,可退出客户端重新登录查看该节点是否存在,来验证临时节点是否被删除)
shell> create -e /node_02 "i is a ephemeral node"
Created /node_02
# 3> 创建一个序列临时节点
shell> create -s -e /node_03 'i is a ephemeral sequence node'
Created /node_03
# 4> 创建一个永久序列节点(节点会自动加上编号)
shell> create -s /node_04 data
Created /node_040000000012
shell> create -s /node_04 data
Created /node_040000000013
shell> create -s /node_04 data
Created /node_040000000014
# 5> 创建一个带权限的节点,限制只能IP为192.168.1.101这台机器访问
## c:创建子节点权限
## d:删除子节点权限
## r:读取子节点列表的权限
## w:写权限,即修改子节点数据权限
## a:管理子节点权限
shell> create /node_04 mydata ip:192.168.1.101:cdrwa
注意:创建节点必须要为节点设置数据,否则会创建不成功。
3、获取节点状态
每个节点都包含描述该节点的一些状态信息,比如:节点数据、版本号等。
语法:stat path [watch]
path:节点全路径
watch:监听节点状态变化
shell> stat /node_01
cZxid = 0x2f
ctime = Sat Nov 12 15:54:05 CST 2016
mZxid = 0x2f
mtime = Sat Nov 12 15:54:05 CST 2016
pZxid = 0x2f
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0
在ZK中,ZK客户端对服务器每一个数据节点的写操作,ZK会认为都是一次完整的事务操作,要么成功,要么失败,保证了数据的原子性。而每次事务都会分配一个唯一的事务id,以标识这次事务操作的数据信息。下面详细理解一下节点状态各个字段的含义:
cZxid:创建节点的事务id
ctime:创建节点的时间
mZxid:修改节点的事务id
mtime:修改节点的时间
pZxid:子节点列表最后一次修改的事务id。删除或添加子节点,不包含修改子节点的数据。
cversion:子节点的版本号,删除或添加子节点,版本号会自增
dataVersion:节点数据版本号,数据写入操作,版本号会递增
aclVersion:节点ACL权限版本,权限写入操作,版本号会递增
ephemeralOwner:临时节点创建时的事务id,如果节点是永久节点,则它的值为0
dataLength:节点数据长度(单位:byte),中文占3个byte
numChildren:子节点数量
4、获取节点数据
语法:get path [watch]
path:节点路径
watch:监听节点数据变化。如果其它客户端修改了该节点的数据,则会通知监听了该节点的所有客户端
shell> get /node_01
mydata
cZxid = 0x2f
ctime = Sat Nov 12 15:54:05 CST 2016
mZxid = 0x2f
mtime = Sat Nov 12 15:54:05 CST 2016
pZxid = 0x2f
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0
/node_01的节点数据为mydata,即节点状态信息的第一行
5、设置节点数据
语法:set path data [version]
path:节点路径
data:节点数据
version:数据版本号(节点状态dataVersion的值)
shell> set /node_01 hello
cZxid = 0x2f
ctime = Sat Nov 12 15:54:05 CST 2016
mZxid = 0x30
mtime = Sat Nov 12 15:55:01 CST 2016
pZxid = 0x2f
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
此时可以看到dataVersion状态的值变成了1。默认不加版本号则会覆盖节点之前设置的数据,如果加上版本号,版本号必须和服务器上的版本号一致,否则会报错,如下所示:
shell> set /node_01 updatedata 2
version No is not valid : /node_01
这种机制和数据库中的乐观索机制非常相似:
想象一种场景,在获取某个节点的数据之后,利用数据处理完业务逻辑,不加版本号,直接修改节点的数据。但在获取和修改节点数据的这一小段时间窗内,很有可能有其它客户端也修改了该节点的数据,而节点数据变化会使节点状态的dataVersion值递增。如果我们获取节点数据处理完成自己的业务逻辑,然后不加上版本号直接修改节点数据时,则会覆盖掉其它客户端修改的最新数据,从而导致数据不一致的情况。所以要保证数据的一致性时,修改节点数据时,应该加上最新的版本号。而在这个场景中,我们在处理完业务逻辑,再修改节点数据时带上节点的版本号,这时若有其它节点修改了数据,修改则会失败。此时我们应该马上再获取一次节点的最新版本号,再做修改。
6、查询子节点列表及状态信息
语法:ls2 path [watch]
path:节点路径
watch:是否监听子节点列表变化通知
# 先在/node_1节点下创建几个子节点
shell> create /node_01/node_01_01 abc
Created /node_01/node_01_01
shell> create /node_01/node_01_02 def
Created /node_01/node_01_02
shell> ls2 /node_01
[node_01_01, node_01_02]
cZxid = 0x2f
ctime = Sat Nov 12 15:54:05 CST 2016
mZxid = 0x30
mtime = Sat Nov 12 15:55:01 CST 2016
pZxid = 0x39
cversion = 2
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 2
第一行是/node_01的子节点列表,后面的信息是/node_01节点的状态信息。和ls命令不一样的是,ls2不仅能查询节点的子节点列表,同时也能查询到节点的状态信息。
7、删除节点
语法:delete path [version]
path:节点路径
version:节点版本号(节点状态cversion的值),可选。如果传递了版本号,则必须保证和服务器的版本号一致,否则会报错:version No is not valid : 节点路径
shell> delete /path/node_01/node_01_01
注意:delete只能删除没有子节点的节点,否则会报错,如下所示:
shell> delete /node_01
Node not empty: /node_01
8、删除节点(包括子节点)
语法:rmr path
path:节点路径
shell> rmr /node_01
rmr会递归删除子节点,再删除节点本身