在ZooKeeper的官方文档上说,Apache ZooKeeper致力于开发和维护一个能实现高度可靠的分布式协调的开源服务器
。
那么,开发和维护的这个服务器究竟是什么?能做什么呢?
ZooKeeper顾名思义就是动物园的管理员,所以他做的事情其实就是一个管理员的工作,把这些个不听话的不懂事的“动物们”整理得服服帖帖的,把“动物园”整理得井井有条。这些个“动物们”当然就是这些分布式应用程序,像HBase、Kafka、SorlCloud等众多的知名框架都是使用zookeeper实现分布式协同管理的,它主要就是用来解决分布式应用中经常遇到的数据管理问题如统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等等。
ZooKeeper广泛应用于分布式配置中心、分布式注册中心、分布式锁、分布式队列、集群选举、分布式屏障和发布/订阅等等分布式协调服务中。
但是,协调服务很难做好。它们特别容易出现竞争条件和死锁等错误,所以,ZooKeeper又是怎么实现的呢?
ZooKeeper提供的命名空间很像标准的文件系统,名称是由斜杠 (/) 分隔的一系列路径元素。ZooKeeper 命名空间中的每个节点(znode)都由路径标识。
基于这种数据结构,我们可以自由地增加、删除znode,在一个znode下增加、删除子znode。
有六种类型的znode:
zookeeper.extendedTypesEnabled=true
开启,可以设置过期时间,不过真正的过期时间不一定是所设置的时间,还会受到定时任务的执行时间的影响,因为节点的关闭是由定时任务轮询关闭的。客户端注册监听它关心的任意节点或者目录节点及递归子目录节点:
基础的入门使用推荐大家看官方文档,照着官方文档操作一遍,如果不看文档就接着往下看吧,我根据文档整理了一下。
咱可以先装一个单机版玩玩,ZooKeeper的安装也非常简单,不过需要保证我们的服务器有java环境。
windows系统在官方文档那下载安装包之后,解压,然后备份一下conf目录下的zoo_sample.cfg文件,重命名为zoo.cfg,在ZooKeeper的目录下可以选择建一个data的目录和log的目录,然后按照我下面的配置根据实际情况修改配置就行了:
# The number of milliseconds of each tick]
# 服务器之间或客户端之间维持心跳的时间间隔(毫秒)
tickTime=2000
#
# The number of ticks that the initial
# synchronization phase can take
# 集群中连接到Leader的Follower服务器初始连接时最长能忍受的心跳时间间隔个数
# 当超过10个心跳时间(10*2000ms=20s)Leader还没有收到Follower的返回信息,说明这个Leader连接失败
initLimit=10
#
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
# Leader与Follower之间发送信息请求与应答时间长度,最长不能超过5个心跳时间间隔(5*2000ms=10s)
syncLimit=5
#
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
# Zookeeper的数据保存目录(默认包括事务日志)
# win下的话文件路径要么使用“\\”要么使用“/”,使用“\”会报错的
dataDir=D:\\apache-zookeeper-3.5.8-bin\\data
# 这里我把事务日志放在另外的文件夹log下
dataLogDir=D:\\apache-zookeeper-3.5.8-bin\\log
#
# the port at which the clients will connect
# 使用的端口号
clientPort=2181
#
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
centos系统也是,先用命令wget https://mirror.bit.edu.cn/apache/zookeeper/zookeeper‐3.5.8/apache‐zookeeper‐3.5.8‐bin.tar.gz
下载,然后tar -zxvf apache‐zookeeper‐3.5.8‐bin.tar.gz
解压,在后面我改zoo.cfg的时候嫌麻烦就把解压的目录修改了mv apache‐zookeeper‐3.5.8‐bin zookeeper
,然后就是cp zoo_sample.cfg zoo.cfg
,新建一个数据保存目录和事务日志保存目录(事务保存日志我在这边是建的/usr/local/zk/zookeeper/datalog),修改zoo.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=/usr/local/zk/zookeeper/data
dataLogDir=/usr/local/zk/zookeeper/datalog
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
启动的话,win下就是双击zkServer.cmd启动服务端,然后双击zkCli.cmd打开客户端就可以使用本机的ZooKeeper了;而Linux下就是通过命令bin/zkServer.sh start conf/zoo.cfg启动服务(注意启动文件和检查配置文件的路径),然后bin/zkCli.sh ‐server ip:port连接服务器。
可以在客户端使用help命令看看ZooKeeper支持的命令,而且还可以用tab键提示,接下来就靠大家动动手自己实践节点和通知机制啦~
创建节点:可以使用命令create [-s] [-e] [-c] [-t ttl] path [data] [acl]
创建一个节点,其中[]
内为可选参数:
创建持久化目录节点:create /app
,这个命令创建了一个/app的持久化目录节点。
删除节点:delete /app
这个命令可以将已创建的/app节点进行删除。
创建持久化顺序编号目录节点:create -s /app/p_
,我们可以多创建几个(ls可以查某节点下的目录节点,使用-R可以递归查该节点下的目录节点及其子节点……,必须是R,区分大小写,不可以是r):
[zk: localhost:2181(CONNECTED) 8] ls -R /
/
/app
/zookeeper
/zookeeper/config
/zookeeper/quota
[zk: localhost:2181(CONNECTED) 9] create -s /app/p_
Created /app/p_0000000000
[zk: localhost:2181(CONNECTED) 10] create -s /app/p_
Created /app/p_0000000001
[zk: localhost:2181(CONNECTED) 11] create -s /app/p_
Created /app/p_0000000002
[zk: localhost:2181(CONNECTED) 12] create -s /app/p_
Created /app/p_0000000003
[zk: localhost:2181(CONNECTED) 13] create -s /app/p_
Created /app/p_0000000004
[zk: localhost:2181(CONNECTED) 14] ls -R /
/
/app
/zookeeper
/app/p_0000000000
/app/p_0000000001
/app/p_0000000002
/app/p_0000000003
/app/p_0000000004
/zookeeper/config
/zookeeper/quota
可以看到使用创建的持久化顺序编号目录节点,生成的节点是按顺序编号的,会从0000000000开始,一共10位的数字,并且依次排序,假设我中间删除几个节点或者全部删除,他也会继续编号下去(使用了delete /app/p_000000000X
):
[zk: localhost:2181(CONNECTED) 15] delete /app/p_0000000001
[zk: localhost:2181(CONNECTED) 16] delete /app/p_0000000003
[zk: localhost:2181(CONNECTED) 17] delete /app/p_0000000004
[zk: localhost:2181(CONNECTED) 18] create -s /app/p_
Created /app/p_0000000005
[zk: localhost:2181(CONNECTED) 19] ls -R /
/
/app
/zookeeper
/app/p_0000000000
/app/p_0000000002
/app/p_0000000005
/zookeeper/config
/zookeeper/quota
值得注意的是,ZooKeeper使用的是绝对路径的方式对节点命名,这个大家在使用的时候需要注意。
创建临时目录节点:create -e /ephemeral
,创建完成之后使用ls /
可以看到存在/ephemeral节点,当使用quit
命令退出客户端时,/ephemeral节点将会被删除;注意,不能在临时目录节点下创建子节点;
当临时目录节点被删除时,我们也可以在logs目录下的zookeeper-root-server-master.out
日志文件看到类似Expiring session 0x100007d9a740002, timeout of 30000ms exceeded
,结合上面的图片我们也可以知道临时节点和session相关,当客户端连接断开后默认在30s(session-timeout)之内如果没有发送请求则会将临时目录节点删除。
创建临时编号顺序目录节点:我们先创建一个持久化目录节点/ephemeral-node,然后使用命令create -s -e
在/ephemeral-node节点下创建多个临时目录节点。
[zk: localhost:2181(CONNECTED) 0] create /ephemeral-node
Created /ephemeral-node
[zk: localhost:2181(CONNECTED) 1] create -s -e /ephemeral-node/
Created /ephemeral-node/0000000000
[zk: localhost:2181(CONNECTED) 2] create -s -e /ephemeral-node/0000000005
Created /ephemeral-node/00000000050000000001
[zk: localhost:2181(CONNECTED) 3] create -s -e /ephemeral-node/
Created /ephemeral-node/0000000002
[zk: localhost:2181(CONNECTED) 4] create -s -e /ephemeral-node/
Created /ephemeral-node/0000000003
[zk: localhost:2181(CONNECTED) 5] create -s -e /ephemeral-node/
Created /ephemeral-node/0000000004
[zk: localhost:2181(CONNECTED) 6] create -s -e /ephemeral-node/xxx
Created /ephemeral-node/xxx0000000005
[zk: localhost:2181(CONNECTED) 7] create -s -e /ephemeral-node/ xxx
Created /ephemeral-node/0000000006
[zk: localhost:2181(CONNECTED) 8] ls -R /
/
/app
/ephemeral-node
/zookeeper
/app/p_0000000000
/app/p_0000000002
/app/p_0000000005
/ephemeral-node/0000000000
/ephemeral-node/0000000002
/ephemeral-node/0000000003
/ephemeral-node/0000000004
/ephemeral-node/00000000050000000001
/ephemeral-node/0000000006
/ephemeral-node/xxx0000000005
/zookeeper/config
/zookeeper/quota
[zk: localhost:2181(CONNECTED) 9] quit
……
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0] ls -R /
/
/app
/ephemeral-node
/zookeeper
/app/p_0000000000
/app/p_0000000002
/app/p_0000000005
/zookeeper/config
/zookeeper/quota
可以看到,临时编号顺序目录节点具有临时目录节点的特点(断开连接则被清除)和编号顺序目录节点的特点(从0000000000开始在节点的命名后面进行编号)。
创建容器节点:create -c /container
。容器节点主要用来容纳子节点,如果没有为其创建子节点,容器节点就和持久化目录节点一样持久,如果为其创建子节点,后续其子节点被清空的话,容器节点也会被ZooKeeper清除,ZooKeeper默认每60秒会检查一次容器节点是否应该被清除。
TTL节点:默认TTL节点是无法创建的,如果想要创建TTL节点的话,win系统需要修改bin目录下的zkServer.cmd
文件,在call %JAVA%
后面添加"-Dzookeeper.extendedTypesEnabled=true"
;而Linux系统下,需要修改zkServer.sh
文件,在ZOOMAIN=
后面添加-Dzookeeper.extendedTypesEnabled=true
即可。
现在创建一个TTL节点:create -t 5000 /ttl-node
,其中,5000是指5000ms,即5s之后过期。大家可以试一下,基本上都需要等5s及以上的时间这个节点才会被删除。
其他命令:
创建节点时为节点添加数据:创建任何节点时,都可以为节点添加上数据,比如我创建一个持久化目录节点/data-node:create /data-node data
,节点名后面就是该节点的值,为data;还可以使用命令set /data-node data2
修改节点的值;还可以使用get /data-node
查看节点的数据或者使用stat /data-node
查看节点的状态信息,还可以使用get -s /data-node
查看节点的数据和状态:
[zk: localhost:2181(CONNECTED) 3] create /data-node data
Created /data-node
[zk: localhost:2181(CONNECTED) 4] get /data-node
data
[zk: localhost:2181(CONNECTED) 5] stat /data-node
cZxid = 0x2e
ctime = Mon Feb 07 14:33:42 CST 2022
mZxid = 0x2e
mtime = Mon Feb 07 14:33:42 CST 2022
pZxid = 0x2e
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
[zk: localhost:2181(CONNECTED) 6] set /data-node data2
[zk: localhost:2181(CONNECTED) 7] get /data-node
data2
[zk: localhost:2181(CONNECTED) 8] stat /data-node
cZxid = 0x2e
ctime = Mon Feb 07 14:33:42 CST 2022
mZxid = 0x2f
mtime = Mon Feb 07 14:34:11 CST 2022
pZxid = 0x2e
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 9] get -s /data-node
data2
cZxid = 0x2e
ctime = Mon Feb 07 14:33:42 CST 2022
mZxid = 0x2f
mtime = Mon Feb 07 14:34:11 CST 2022
pZxid = 0x2e
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
其中,对于节点的状态:
修改节点数据:除了使用上述所说的set /path changed
命令之外,还可以使用set -s /path changed
命令修改节点数据的同时展示该节点的状态:
[zk: localhost:2181(CONNECTED) 0] delete /data-node data
[zk: localhost:2181(CONNECTED) 1] create /data-node data
Created /data-node
[zk: localhost:2181(CONNECTED) 2] get -s /data-node
data
cZxid = 0x3b
ctime = Mon Feb 07 16:48:33 CST 2022
mZxid = 0x3b
mtime = Mon Feb 07 16:48:33 CST 2022
pZxid = 0x3b
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
[zk: localhost:2181(CONNECTED) 3] set -s /data-node data
cZxid = 0x3b
ctime = Mon Feb 07 16:48:33 CST 2022
mZxid = 0x3c
mtime = Mon Feb 07 16:49:09 CST 2022
pZxid = 0x3b
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
我们可以看到,虽然我们所修改的数据和原来的数据是一样的,但是该节点的数据版本号还是发生了变化,所以只要使用了set命令修改节点,就算是该节点发生了数据的变化
乐观锁:我们还可以根据状态数据中的版本号在有并发修改的情境下实现乐观锁的功能,我们依旧使用上面的/data-node节点,在上面我们可以看到该节点的dataVersion为1,这时客户端在修改数据的时候可以把版本号带上set -v 1 /data-node data3
,如果在执行这个命令之前已经有人修改了数据,此时的版本号应该是已经被递增了,不是1了,使用在使用上面那个命令去修改数据就会报错:
[zk: localhost:2181(CONNECTED) 9] get -s /data-node
data2
cZxid = 0x2e
ctime = Mon Feb 07 14:33:42 CST 2022
mZxid = 0x2f
mtime = Mon Feb 07 14:34:11 CST 2022
pZxid = 0x2e
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 10] set /data-node data1
[zk: localhost:2181(CONNECTED) 11] set -v 1 /data-node data3
version No is not valid : /data-node
事件监听机制的前提是客户端和服务端建立的长连接且有效,事件监听的通知是由服务端反馈给对应的客户端的。
针对节点的监听:
ZooKeeper针对节点的事件监听机制是一次性的,一旦事件触发,对应的注册立刻被移除。
可以有以下几种方式监听节点:
get -w /path
:注册监听的同时获取数据;stat -w /path
:注册监听的同时查看节点的状态;get -s -w /path
:注册监听的同时获取数据和查看节点的状态;当该节点发生变化时,服务端会主动向客户端推送节点发生变化的通知,只会通知一次,而且是哪台客户端注册的监听,服务端就会将信息推送至该客户端。
针对目录的监听:
目录的变化会触发事件,和针对节点的监听一样,一旦事件触发,对应的监听也会被移除。针对目录进行监听只会监听目录结构是否发生变化(即目录下是否有增加或删除节点)
可以有以下两种方式监听目录:
ls -w /path
:针对某一目录进行监听;ls -R -w /path
:针对某一目录递归监听(即对ls -R /path
这些目录进行监听)。对于ls -R -w /path
,举个例子:
[zk: localhost:2181(CONNECTED) 7] ls -R -w /path
/path
/path/sub0
/path/sub1
/path/sub0/sub0-sub0
/path/sub0/sub0-sub1
[zk: localhost:2181(CONNECTED) 8] create /path/sub2
WATCHER::
Created /path/sub2
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/path
[zk: localhost:2181(CONNECTED) 9] create /path/sub3
Created /path/sub3
[zk: localhost:2181(CONNECTED) 10] create /path/sub0/sub0-sub2
WATCHER::
Created /path/sub0/sub0-sub2
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/path/sub0
[zk: localhost:2181(CONNECTED) 11] create /path/sub0/sub0-sub3
Created /path/sub0/sub0-sub3
[zk: localhost:2181(CONNECTED) 12] create /path/sub1/sub1-sub2
WATCHER::
Created /path/sub1/sub1-sub2
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/path/sub1
[zk: localhost:2181(CONNECTED) 14] create /path/sub0/sub0-sub1/sub0-sub1-sub0
WATCHER::
Created /path/sub0/sub0-sub1/sub0-sub1-sub0
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/path/sub0/sub0-sub1
移除监听:可以使用removewatches /path
对该节点上的监听移除。
服务端推送至客户端的通知一般是:
WATCHER::
WatchedEvent state:SyncConnected type:事件类型 path:/节点路径
事件类型有:
ZooKeeper也是有保证数据安全性的机制的,那就是ACL权限控制,它由以下三部分组成:
用来设置ZooKeeper服务器进行权限验证的方式,有以下四种模式:
world:默认的方式,登录ZooKeeper的所有人都可以使用,而且包括所有的权限信息,比方我们创建一个节点,然后使用getAcl命令就可以看到该节点的ACL信息:
[zk: localhost:2181(CONNECTED) 0] create /path
Created /scheme-digest-setacl
[zk: localhost:2181(CONNECTED) 1] getAcl /path
'world,'anyone
: cdrwa
可以从该节点的ACL信息中了解到,world的权限模式可以让anyone使用。
范围验证:ZooKeeper可以对某个或某段IP地址授予某种权限。
比方说此时我的计算机的IP是192.168.187.1,当我想创建一个节点:
[zk: localhost:2181(CONNECTED) 0] create -e /scheme-ip1 ip:192.168.187.120:cdwra
Created /scheme-ip1
[zk: localhost:2181(CONNECTED) 1] get /scheme-ip1
ip:192.168.187.120:cdwra
[zk: localhost:2181(CONNECTED) 2] create -e /scheme-ip2 data ip:192.168.187.120:cdwra
Created /scheme-ip2
[zk: localhost:2181(CONNECTED) 3] get /scheme-ip2
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /scheme-ip2
创建的第一个节点发现并没有给/scheme-ip1加上权限控制,因为创建节点给IP授权时的命令格式应该是create [-s] [-e] [-c] [-t ttl] path data ip:IP1:Permission1,ip:IP2:Permission2……
(“IP:”后面是为这个IP所授予的权限信息,多个指定IP可以通过逗号分隔);创建的第二个节点没有给本地机器授权,所以本地机器也就没有办法使用这个节点了,不过此时,192.168.187.120的机器却是可以使用这个节点:
2022-02-10 16:15:25,194 [myid:192.168.187.1:2181] - INFO [main-SendThread(192.168.187.1:2181):ClientCnxn$SendThread@959] - Socket connection established, initiating session, client: /192.168.187.120:53260, server: 192.168.187.1/192.168.187.1:2181
2022-02-10 16:15:25,217 [myid:192.168.187.1:2181] - INFO [main-SendThread(192.168.187.1:2181):ClientCnxn$SendThread@1394] - Session establishment complete on server 192.168.187.1/192.168.187.1:2181, sessionid = 0x100667b93720002, negotiated timeout = 30000
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
[zk: 192.168.187.1:2181(CONNECTED) 0] get -s /scheme-ip2
data
cZxid = 0x60
ctime = Thu Feb 10 16:15:30 CST 2022
mZxid = 0x60
mtime = Thu Feb 10 16:15:30 CST 2022
pZxid = 0x60
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x100667b93720003
dataLength = 4
numChildren = 0
[zk: 192.168.187.1:2181(CONNECTED) 1] set -s /scheme-ip2 data2
cZxid = 0x60
ctime = Thu Feb 10 16:15:30 CST 2022
mZxid = 0x61
mtime = Thu Feb 10 16:17:34 CST 2022
pZxid = 0x60
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x100667b93720003
dataLength = 5
numChildren = 0
[zk: 192.168.187.1:2181(CONNECTED) 2] delete /scheme-ip2
[zk: 192.168.187.1:2181(CONNECTED) 3]
口令验证:相当于使用账号密码进行验证的,在ZooKeeper中这种验证方式是Digest认证,需要现在客户端传送“username:password”这种形式的权限表示符之后,由ZooKeeper对密码使用SHA-1和BASE算法进行加密来保证信息的安全性。
我们可以通过程序获得加密的密文:
@Test
public void generateSuperDigest() throws NoSuchAlgorithmException {
String sId = DigestAuthenticationProvider.generateDigest("username:password");
System.out.println(sId);
}
或者在xshell中直接用命令生成:
echo -n : | openssl dgst -binary -sha1 | openssl base64
比方我打算创建一个user:pass
的用户,通过echo -n user:pass | openssl dgst -binary -sha1 | openssl base64
得到密文smGaoVKd/cQkjm7b88GyorAUz20=
,然后:
[zk: localhost:2181(CONNECTED) 0] create /scheme-digest data digest:user:smGaoVKd/cQkjm7b88GyorAUz20=:cdrwa
Created /scheme-digest
[zk: localhost:2181(CONNECTED) 1] get -s /scheme-digest
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /scheme-digest
[zk: localhost:2181(CONNECTED) 2] addauth digest user:pass
[zk: localhost:2181(CONNECTED) 3] get -s /scheme-digest
data
cZxid = 0x13
ctime = Thu Feb 10 17:25:45 CST 2022
mZxid = 0x13
mtime = Thu Feb 10 17:25:45 CST 2022
pZxid = 0x13
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
[zk: localhost:2181(CONNECTED) 4] create /scheme-digest-1 data auth:user:pass:cdrwa
Created /scheme-digest-1
[zk: localhost:2181(CONNECTED) 5] get -s /scheme-digest-1
data
cZxid = 0x16
ctime = Thu Feb 10 17:34:34 CST 2022
mZxid = 0x16
mtime = Thu Feb 10 17:34:34 CST 2022
pZxid = 0x16
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
可以看到,在授权的时候需要使用密文,授权完成之后如果没有“登录”,也是没有办法使用的,只有为当前的客户端添加上权限,认证成功之后,才可以使用;
而当前客户端添加上权限之后,为节点添加ACL权限时可以不需要再使用密文授权了,可以直接使用明文,切记是以create [-s] [-e] [-c] [-t ttl] path data auth:
的格式进行授权的,如果是用digest
,ZooKeeper会认为你使用的是密文,这个节点你就真的用不了了:
[zk: localhost:2181(CONNECTED) 6] create /scheme-digest1 data digest:user:pass:cdrwa
Created /scheme-digest1
[zk: localhost:2181(CONNECTED) 7] get -s /scheme-digest1
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /scheme-digest1
[zk: localhost:2181(CONNECTED) 8] addauth digest user:pass
[zk: localhost:2181(CONNECTED) 9] get -s /scheme-digest1
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /scheme-digest1
另外说一句,同一个客户端是可以添加多个用户的,比如:
[zk: localhost:2181(CONNECTED) 0] addauth digest user1:pass1
[zk: localhost:2181(CONNECTED) 1] addauth digest user2:pass2
[zk: localhost:2181(CONNECTED) 2] addauth digest user3:pass3
如果退出客户端了,想要访问对应的节点,还是需要重新添加用户才能访问的。
Super权限模式:Super可以认为是一种特殊的Digest认证。具有Super权限的客户端可以对ZooKeeper上的任意数据节点进行任意操作。使用Super权限模式需要在启动类上通过JVM 系统参数开启,我们可以通过修改配置文件开启Super权限模式:
-Dzookeeper.DigestAuthenticationProvider.superDigest=:))>
然后我们需要修改zkServer.sh文件,添加上这句话:"-Dzookeeper.DigestAuthenticationProvider.superDigest=super:gG7s8t3oDEtIqF6DM9LlI/R+9Ss="
前面我们的/scheme-ip-setacl节点和/scheme-digest-setacl是分别设置了范围验证和口令验证的,我们试一下看看能不能直接使用super用户直接使用这两个节点:
[zk: localhost:2181(CONNECTED) 0] get /scheme-ip-setacl
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /scheme-ip-setacl
[zk: localhost:2181(CONNECTED) 1] get /scheme-digest-setacl
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /scheme-digest-setacl
[zk: localhost:2181(CONNECTED) 2] addauth digest super:super
[zk: localhost:2181(CONNECTED) 3] get /scheme-ip-setacl
null
[zk: localhost:2181(CONNECTED) 4] get /scheme-digest-setacl
null
可以看到,在super模式下,不管是范围验证还是口令验证,super用户都可以直接访问。
授权对象就是说我们要把权限赋予谁,而对应于4种不同的权限模式来说,如果我们选择采用IP方式,使用的授权对象可以是一个IP地址或IP地址段;而如果使用Digest或Super方式,则对应于一个用户名。如果是World模式,是授权系统中所有的用户(anyone)。
即我们可以在数据节点上执行的操作,ZooKeeper中定义了以下5中权限:
设置ACL的方式除了上面在权限模式中展示的方式(创建节点的同时)之外,还可以使用set命令对已经创建的节点进行设置ACL:
[zk: localhost:2181(CONNECTED) 0] setAcl /scheme-ip-setacl ip:192.168.187.1:cdwra
Node does not exist: /scheme-ip-setacl
[zk: localhost:2181(CONNECTED) 1] create /scheme-ip-setacl
Created /scheme-ip-setacl
[zk: localhost:2181(CONNECTED) 2] create /scheme-digest-setacl
Created /scheme-digest-setacl
[zk: localhost:2181(CONNECTED) 3] getAcl /scheme-ip-setacl
'world,'anyone
: cdrwa
[zk: localhost:2181(CONNECTED) 4] getAcl /scheme-digest-setacl
'world,'anyone
: cdrwa
[zk: localhost:2181(CONNECTED) 5] setAcl /scheme-ip-setacl ip:192.168.187.1:cdwra
[zk: localhost:2181(CONNECTED) 6] setAcl /scheme-digest-setacl digest:user:smGaoVKd/cQkjm7b88GyorAUz20=:cdwra
[zk: localhost:2181(CONNECTED) 7] get /scheme-ip-setacl
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /scheme-ip-setacl
[zk: localhost:2181(CONNECTED) 8] get /scheme-digest-setacl
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /scheme-digest-setacl
[zk: localhost:2181(CONNECTED) 9] addauth digest user:pass
[zk: localhost:2181(CONNECTED) 10] get /scheme-digest-setacl
null
[zk: localhost:2181(CONNECTED) 11] getAcl /scheme-digest-setacl
'digest,'user:smGaoVKd/cQkjm7b88GyorAUz20=
: cdrwa
[zk: localhost:2181(CONNECTED) 12] create /scheme-digest-setacl-1
Created /scheme-digest-setacl-1
[zk: localhost:2181(CONNECTED) 13] getAcl /scheme-digest-setacl-1
'world,'anyone
: cdrwa
[zk: localhost:2181(CONNECTED) 14] setAcl /scheme-digest-setacl-1 auth:user:pass:cdrwa
[zk: localhost:2181(CONNECTED) 15] getAcl /scheme-digest-setacl-1
'digest,'user:smGaoVKd/cQkjm7b88GyorAUz20=
: cdrwa
--------------在192.168.187.1客户端连接192.168.187.120服务端----------------
[zk: localhost:2181(CONNECTED) 0] connect 192.168.187.120:2181
WATCHER::
……
2022-02-11 16:26:11,074 [myid:192.168.187.120:2181] - INFO [main-SendThread(192.168.187.120:2181):ClientCnxn$SendThread@959] - Socket connection established, initiating session, client: /192.168.187.1:63108, server: 192.168.187.120/192.168.187.120:2181
2022-02-11 16:26:11,077 [myid:192.168.187.120:2181] - INFO [main-SendThread(192.168.187.120:2181):ClientCnxn$SendThread@1394] - Session establishment complete on server 192.168.187.120/192.168.187.120:2181, sessionid = 0x100007d9a74000a, negotiated timeout = 30000
[zk: 192.168.187.120:2181(CONNECTED) 1] get /scheme-ip-setacl
null
[zk: 192.168.187.120:2181(CONNECTED) 2] getAcl /scheme-ip-setacl
'ip,'192.168.187.1
: cdrwa
从上面可以看到:
addauth digest :
注册用户信息之后也可以使用setAcl命令明文授权,但是如果创建节点不授权的话该节点依旧还是使用默认的权限。我们可以修改配置文件zoo.cnf,设置跳过ACL验证skipACL=yes
(默认为no),修改之后重启服务就可以对忘记密码的节点重新授权了:
[zk: localhost:2181(CONNECTED) 0] get /scheme-digest-setacl
null
[zk: localhost:2181(CONNECTED) 1] get /scheme-ip-setacl
null
[zk: localhost:2181(CONNECTED) 2] setAcl /scheme-digest-setacl digest:user:smGaoVKd/cQkjm7b88GyorAUz20=:cdwra
[zk: localhost:2181(CONNECTED) 3] setAcl /scheme-ip-setacl ip:192.168.187.120:cdwra
[zk: localhost:2181(CONNECTED) 4]
修改完成之后记得将配置文件中的skipACL=yes
改为no或者直接删除,不然就没有权限验证的功能了。
如果有super用户的话直接使用super用户重新授权即可:
[zk: localhost:2181(CONNECTED) 0] get /scheme-digest-setacl
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /scheme-digest-setacl
[zk: localhost:2181(CONNECTED) 1] addauth digest super:super
[zk: localhost:2181(CONNECTED) 2] get /scheme-digest-setacl
null
[zk: localhost:2181(CONNECTED) 3] getAcl /scheme-digest-setacl
'digest,'user:smGaoVKd/cQkjm7b88GyorAUz20=
: cdrwa
[zk: localhost:2181(CONNECTED) 4] setAcl /scheme-digest-setacl auth super:super:cwrad
auth does not have the form scheme:id:perm
Acl is not valid : /scheme-digest-setacl
[zk: localhost:2181(CONNECTED) 5] setAcl /scheme-digest-setacl digest super:gG7s8t3oDEtIqF6DM9LlI/R+9Ss=:cwrad
digest does not have the form scheme:id:perm
Acl is not valid : /scheme-digest-setacl
[zk: localhost:2181(CONNECTED) 6] setAcl /scheme-digest-setacl digest user1:+7K83PhyQ3ijGj0ADmljf0quVwQ=:cwrad
digest does not have the form scheme:id:perm
Acl is not valid : /scheme-digest-setacl
通过上面的尝试我还发现不能给节点授予超管的权限。
ZooKeeper通过使用事务日志和数据快照来实现数据的持久化(事务日志文件和数据快照文件有点像Redis的aof文件和rdb文件)。
从上面我的配置文件可以知道,我的事务日志本来会和快照数据存放在一起的,不过因为我加了一个dataLogDir
的配置,所以我的事务日志现在是存放在D:\apache-zookeeper-3.5.8-bin\log路径下的,如图所示:
从图中我们也可以看到:
log.十六进制数
的格式命名的,且大小都是统一的65537KB,因为ZooKeeper进行事务日志文件操作时会频繁进行磁盘IO操作,事务日志不断追加写操作会触发底层磁盘IO为文件开辟新的磁盘块(磁盘Seek)。为了提升磁盘 IO效率,ZooKeeper在创建事务日志时会进行文件空间的预分配,预分配的磁盘大小可以通过系统参数zookeeper.preAllocSize
进行配置。我们想要打开事务日志文件看ZooKeeper的事务日志的话需要使用zk为我们提供的格式化工具org.apache.zookeeper.server.LogFormatter
进行查看,还需要引用zk的lib目录下面这3个jar包(log4j的包是为了格式化日志的):
D:\apache-zookeeper-3.5.8-bin\lib>java -cp slf4j-api-1.7.25.jar:zookeeper-3.5.8.jar:zookeeper-jute-3.5.8.jar org.apache.zookeeper.server.LogFormatter D:\apache-zookeeper-3.5.8-bin\log\version-2 log.f
错误: 找不到或无法加载主类 org.apache.zookeeper.server.LogFormatter
D:\apache-zookeeper-3.5.8-bin\lib>java -classpath .:slf4j-api-1.7.25.jar:zookeeper-3.5.8.jar:zookeeper-jute-3.5.8.jar org.apache.zookeeper.server.LogFormatter D:\apache-zookeeper-3.5.8-bin\log\version-2\log.f
错误: 找不到或无法加载主类 org.apache.zookeeper.server.LogFormatter
上面就是使用jar包打开日志的命令,但是都报了找不到或无法加载主类 org.apache.zookeeper.server.LogFormatter
的错误,网上查不到答案,很疑惑,明明我的jar包也是存在的,有小伙伴要是知道为什么的话可以在评论区留下答案。
不过我在centOS上也有装ZooKeeper,我先做了这几件事:
[zk: localhost:2181(CONNECTED) 0] create /path data
Created /path
[zk: localhost:2181(CONNECTED) 1] stat /path
cZxid = 0x9
ctime = Thu Feb 10 14:00:33 CST 2022
mZxid = 0x9
mtime = Thu Feb 10 14:00:33 CST 2022
pZxid = 0x9
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
[zk: localhost:2181(CONNECTED) 2] create -e /ephemecral
Created /ephemecral
[zk: localhost:2181(CONNECTED) 3] set /path xxxx
[zk: localhost:2181(CONNECTED) 4] get -s /path
xxxx
cZxid = 0x9
ctime = Thu Feb 10 14:00:33 CST 2022
mZxid = 0xb
mtime = Thu Feb 10 14:10:20 CST 2022
pZxid = 0x9
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
[zk: localhost:2181(CONNECTED) 5] create -s -e /e
Created /e0000000014
[zk: localhost:2181(CONNECTED) 6] ls /
[e0000000014, ee, ephemecral, path, scheme-digest, scheme-digest-1, scheme-digest-setacl, scheme-digest-setacl-1, scheme-digest1, scheme-ip-setacl, zookeeper]
[zk: localhost:2181(CONNECTED) 7] delete /path
[zk: localhost:2181(CONNECTED) 8] create -e -s /eee
Created /eee0000000015
[zk: localhost:2181(CONNECTED) 13] setAcl /ee ip:192.168.187.120:cwrad
然后使用命令查看日志:
2/10/22 2:00:27 PM CST session 0x100007d9a740003 cxid 0x0 zxid 0x8 createSession 30000
2/10/22 2:00:33 PM CST session 0x100007d9a740003 cxid 0x1 zxid 0x9 create '/path,#64617461,v{s{31,s{'world,'anyone}}},F,13
2/10/22 2:03:34 PM CST session 0x100007d9a740003 cxid 0x3 zxid 0xa create '/ephemecral,,v{s{31,s{'world,'anyone}}},T,14
2/10/22 2:10:20 PM CST session 0x100007d9a740003 cxid 0x4 zxid 0xb setData '/path,#78787878,1
2/10/22 2:28:46 PM CST session 0x100007d9a740003 cxid 0x5 zxid 0xc create '/e0000000014,,v{s{31,s{'world,'anyone}}},T,15
2/10/22 2:34:51 PM CST session 0x100007d9a740003 cxid 0x7 zxid 0xd delete '/path
2/10/22 2:34:57 PM CST session 0x100007d9a740003 cxid 0x8 zxid 0xe create '/e0000000017,,v{s{31,s{'world,'anyone}}},T,16
2/10/22 2:06:56 PM CST session 0x100007d9a740003 cxid 0x9 zxid 0xf setACL '/ee,v{s{31,s{'ip,'192.168.187.120}}},1
可以了解到:
事务日志不会记录get、stat、ls等查询操作。
日志中从左到右分别记录了操作时间、客户端会话ID、cxid(客户端事务id)、zxid(事务id)、操作类型、节点路径。
如果是修改节点的值,在节点路径后面从左到右记录节点数据(用#+ascii 码表示)、节点版本;
如果是创建节点的话,在节点路径后面从左到右记录节点的权限信息,T或F代表是否是临时节点,最后面的数字则表示当前父节点的子节点版本数(创建一个节点则会自增1,节点删除后这个版本数保存不变);
如果是修改节点权限,在节点路径后面从左到右记录节点的权限信息、节点版本。
当打开一个客户端时,cxid会从0开始,并且打印一个创建会话的日志。
当执行get命令时,cxid会自增1,而zxid不会自增,并且不会打印日志。
事务日志是有顺序的,按照操作的顺序记录数据,而且我们可以看到,事务日志的文件名的后缀就是日志中最小的那个事务id,其实,事务日志的命名方式就是log.<当时最大事务ID>,在日志满了(前面也说了,文件大小是预分配的)就会进行下一次事务日志的创建。
相对于事务日志,数据快照文件中的数据是乱序的,是记录ZooKeeper服务器上某一时刻的全量数据,并将其写入指定的磁盘文件中。
在zoo.cfg中除了可以配置快照文件的存储目录,还可以配置一个snapCount,自定义每隔snapCount个事务请求,就生成一次快照。
在集群中,为了避免所有的机器在同一时间进行快照,实际的快照生成时间为事务数达到[snapCount/2 + 随机数(随机数范围为1 ~ snapCount/2 )] 个数时开始快照。
数据快照文件也是二进制文件,需要org.apache.zookeeper.server.SnapshotFormatter工具进行格式化,同样的win的zk也是报错了:
D:\apache-zookeeper-3.5.8-bin\lib>java -cp slf4j-api-1.7.25.jar:zookeeper-3.5.8.jar:zookeeper-jute-3.5.8.jar org.apache.zookeeper.server.SnapshotFormatter D:\apache-zookeeper-3.5.8-bin\data\version-2\snapshot.f
错误: 找不到或无法加载主类 org.apache.zookeeper.server.SnapshotFormatter
D:\apache-zookeeper-3.5.8-bin\lib>java -classpath .:slf4j-api-1.7.25.jar:zookeeper-3.5.8.jar:zookeeper-jute-3.5.8.jar org.apache.zookeeper.server.SnapshotFormatter D:\apache-zookeeper-3.5.8-bin\data\version-2\snapshot.f
错误: 找不到或无法加载主类 org.apache.zookeeper.server.SnapshotFormatter
所以我还是只能演示我centOS上的zk服务器:
[root@master lib]# java -cp slf4j-api-1.7.25.jar:zookeeper-3.5.8.jar:zookeeper-jute-3.5.8.jar org.apache.zookeeper.server.SnapshotFormatter /usr/local/zk/zookeeper/data/version-2/snapshot.39
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
ZNode Details (count=13):
----
/
cZxid = 0x00000000000000
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x00000000000000
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x00000000000029
cversion = 12
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x00000000000000
dataLength = 0
----
/path
cZxid = 0x00000000000009
ctime = Thu Feb 10 14:00:33 CST 2022
mZxid = 0x0000000000000b
mtime = Thu Feb 10 14:10:20 CST 2022
pZxid = 0x00000000000009
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x00000000000000
dataLength = 4
----
/zookeeper
cZxid = 0x00000000000000
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x00000000000000
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x00000000000000
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x00000000000000
dataLength = 0
----
/zookeeper/config
cZxid = 0x00000000000000
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x00000000000000
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x00000000000000
cversion = 0
dataVersion = 0
aclVersion = -1
ephemeralOwner = 0x00000000000000
dataLength = 0
----
/zookeeper/quota
cZxid = 0x00000000000000
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x00000000000000
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x00000000000000
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x00000000000000
dataLength = 0
----
/scheme-digest-1
cZxid = 0x00000000000016
ctime = Thu Feb 10 17:34:34 CST 2022
mZxid = 0x00000000000016
mtime = Thu Feb 10 17:34:34 CST 2022
pZxid = 0x00000000000016
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x00000000000000
dataLength = 4
----
/node-ip
cZxid = 0x0000000000000d
ctime = Thu Feb 10 15:40:52 CST 2022
mZxid = 0x0000000000000d
mtime = Thu Feb 10 15:40:52 CST 2022
pZxid = 0x0000000000000d
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x00000000000000
dataLength = 22
----
/scheme-ip-setacl
cZxid = 0x0000000000001c
ctime = Thu Feb 10 18:05:09 CST 2022
mZxid = 0x0000000000001c
mtime = Thu Feb 10 18:05:09 CST 2022
pZxid = 0x0000000000001c
cversion = 0
dataVersion = 0
aclVersion = 2
ephemeralOwner = 0x00000000000000
no data
----
/scheme-digest1
cZxid = 0x00000000000014
ctime = Thu Feb 10 17:32:40 CST 2022
mZxid = 0x00000000000014
mtime = Thu Feb 10 17:32:40 CST 2022
pZxid = 0x00000000000014
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x00000000000000
dataLength = 4
----
/scheme-digest-setacl
cZxid = 0x0000000000001d
ctime = Thu Feb 10 18:05:25 CST 2022
mZxid = 0x0000000000001d
mtime = Thu Feb 10 18:05:25 CST 2022
pZxid = 0x0000000000001d
cversion = 0
dataVersion = 0
aclVersion = 3
ephemeralOwner = 0x00000000000000
no data
----
/scheme-digest
cZxid = 0x00000000000013
ctime = Thu Feb 10 17:25:45 CST 2022
mZxid = 0x00000000000013
mtime = Thu Feb 10 17:25:45 CST 2022
pZxid = 0x00000000000013
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x00000000000000
dataLength = 4
----
/scheme-digest-setacl-1
cZxid = 0x00000000000029
ctime = Fri Feb 11 16:31:59 CST 2022
mZxid = 0x00000000000029
mtime = Fri Feb 11 16:31:59 CST 2022
pZxid = 0x00000000000029
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x00000000000000
no data
----
Session Details (sid, timeout, ephemeralCount):
这个快照文件和我当前的zk服务器中的节点情况还是有出入的,因为还没有达到他拍快照的条件,快照的命名方式同样的,也是snapshot.<当时最大事务ID>,当然不是预分配了,毕竟就写一次,只需要达到一定的事务数就进行一次快照。
像极了redis,快照数据主要是为了快速恢复,事务日志文件是每次事务请求都会进行追加的操作,而快照是达到某种设定条件下的内存全量数据。所以通常快照数据是反映当时内存数据的状态。事务日志是更全面的数据,所以恢复数据的时候,可以先恢复快照数据,再通过增量恢复事务日志中的数据即可。