ZooKeeper‘s Bizarre Adventure——Volume Ⅰ:初识ZooKeeper

What is ZooKeeper?

在ZooKeeper的官方文档上说,Apache ZooKeeper致力于开发和维护一个能实现高度可靠的分布式协调的开源服务器
那么,开发和维护的这个服务器究竟是什么?能做什么呢?
ZooKeeper顾名思义就是动物园的管理员,所以他做的事情其实就是一个管理员的工作,把这些个不听话的不懂事的“动物们”整理得服服帖帖的,把“动物园”整理得井井有条。这些个“动物们”当然就是这些分布式应用程序,像HBase、Kafka、SorlCloud等众多的知名框架都是使用zookeeper实现分布式协同管理的,它主要就是用来解决分布式应用中经常遇到的数据管理问题如统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等等。

ZooKeeper的应用场景

ZooKeeper广泛应用于分布式配置中心、分布式注册中心、分布式锁、分布式队列、集群选举、分布式屏障和发布/订阅等等分布式协调服务中。
但是,协调服务很难做好。它们特别容易出现竞争条件和死锁等错误,所以,ZooKeeper又是怎么实现的呢?

ZooKeeper的两大核心概念

文件系统数据结构

ZooKeeper提供的命名空间很像标准的文件系统,名称是由斜杠 (/) 分隔的一系列路径元素。ZooKeeper 命名空间中的每个节点(znode)都由路径标识。
在这里插入图片描述
基于这种数据结构,我们可以自由地增加、删除znode,在一个znode下增加、删除子znode。
有六种类型的znode:

  1. PERSISTENT(持久化目录节点):客户端与ZooKeeper断开连接后,该节点依旧存在,只要不将该节点删除,它将永久存在;
  2. PERSISTENT_SEQUENTIAL(持久化顺序编号目录节点):客户端与ZooKeeper断开连接后,该节点依旧存在,只是ZooKeeper为该节点名称进行顺序编号;
  3. EPHEMERAL(临时目录节点):客户端与ZooKeeper断开连接后,该节点将会被删除;
  4. EPHEMERAL_SEQUENTIAL(临时顺序编号目录节点):ZooKeeper会为其节点名称进行顺序编号,同时它也会在客户端断开连接时被删除;
  5. Container(容器节点):3.5.3版本新增,如果容器节点下面没有子节点,则在未来会被ZooKeeper自动清除,定时任务默认60s检查一次;
  6. TTL节点:默认禁用,只能通过系统配置zookeeper.extendedTypesEnabled=true开启,可以设置过期时间,不过真正的过期时间不一定是所设置的时间,还会受到定时任务的执行时间的影响,因为节点的关闭是由定时任务轮询关闭的。
    其中,临时节点的生命周期是与session相关的,当sessionid过期,也就是连接断开或者关闭之后,在session-timeout时间内没有重新连接,则会删除:
    在这里插入图片描述

监听通知机制

客户端注册监听它关心的任意节点或者目录节点及递归子目录节点:

  1. 如果注册的是对某个节点的监听,则当这个节点被删除或者被修改时,对应的客户端将被通知;
  2. 如果注册的是对某个目录的监听,则当这个目录有子节点被创建或者被删除时,对应的客户端将被通知;
  3. 如果注册的时是对某个目录的递归子节点进行监控,则当这个目录下面的任意子节点有目录结构变化(有子节点被创建或被删除)或者根节点有数据变化时,对应的客户端将被通知。
    注意:所有的通知都是一次性的,无论是对节点还是对目录进行监听,一旦触发,对应的监听则被移除。对递归子节点的监听,所监听的是对所有子节点的,所以每个子节点下面的事件同样只会被触发一次。

实践

基础的入门使用推荐大家看官方文档,照着官方文档操作一遍,如果不看文档就接着往下看吧,我根据文档整理了一下。

安装

咱可以先装一个单机版玩玩,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键提示,接下来就靠大家动动手自己实践节点和通知机制啦~

节点的使用命令

  1. 创建节点:可以使用命令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及以上的时间这个节点才会被删除。

  2. 其他命令:

    • 创建节点时为节点添加数据:创建任何节点时,都可以为节点添加上数据,比如我创建一个持久化目录节点/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
      

      其中,对于节点的状态:

      • cZxid:创建该节点的事务ID
      • mZxid:修改该节点的事务ID
      • ctime和mtime分别对应该节点的创建时间和修改时间
      • pZxid:最后添加或删除子节点的事务ID
      • cversion:该节点的子节点的结果集版本,该节点的子节点增加或删除都会影响这个版本
      • dataVersion:该节点的当前数据版本,在上面的例子我修改了节点的数据,这个版本号就发生变化了
      • aclVersion:该节点的acl版本
      • ephemeralOwner:如果该节点是临时节点,则表示为该临时节点的所有者的session ID,如果该节点不是临时节点,则置为0
      • dataLength:该节点的数据字段的长度
      • numChildren:该节点的子节点个数
    • 修改节点数据:除了使用上述所说的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
      

事件监听机制

事件监听机制的前提是客户端和服务端建立的长连接且有效,事件监听的通知是由服务端反馈给对应的客户端的。

  1. 针对节点的监听:

    ZooKeeper针对节点的事件监听机制是一次性的,一旦事件触发,对应的注册立刻被移除。

    可以有以下几种方式监听节点:

    • get -w /path:注册监听的同时获取数据;
    • stat -w /path:注册监听的同时查看节点的状态;
    • get -s -w /path:注册监听的同时获取数据和查看节点的状态;

    当该节点发生变化时,服务端会主动向客户端推送节点发生变化的通知,只会通知一次,而且是哪台客户端注册的监听,服务端就会将信息推送至该客户端。

  2. 针对目录的监听:

    目录的变化会触发事件,和针对节点的监听一样,一旦事件触发,对应的监听也会被移除。针对目录进行监听只会监听目录结构是否发生变化(即目录下是否有增加或删除节点)

    可以有以下两种方式监听目录:

    • 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
    
  3. 移除监听:可以使用removewatches /path对该节点上的监听移除。

  4. 服务端推送至客户端的通知一般是:

    WATCHER::
    
    WatchedEvent state:SyncConnected type:事件类型 path:/节点路径
    

    事件类型有:

    • None:连接建立事件;
    • NodeCreated:节点创建;
    • NodeDeleted:节点删除;
    • NodeDataChanged:节点数据变化;
    • NodeChildrenChanged:子节点列表变化;
    • DataWatchRemoved:节点监听被移除;
    • ChildWatchRemoved:子节点监听被移除。

ACL权限控制(Access Control List)

ZooKeeper也是有保证数据安全性的机制的,那就是ACL权限控制,它由以下三部分组成:

权限模式(Scheme)

用来设置ZooKeeper服务器进行权限验证的方式,有以下四种模式:

  1. 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使用。

  2. 范围验证: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] 
    
  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 <username>:<password> | 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:<username>:<password>:Permission的格式进行授权的,如果是用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
    

    如果退出客户端了,想要访问对应的节点,还是需要重新添加用户才能访问的。

  4. Super权限模式:Super可以认为是一种特殊的Digest认证。具有Super权限的客户端可以对ZooKeeper上的任意数据节点进行任意操作。使用Super权限模式需要在启动类上通过JVM 系统参数开启,我们可以通过修改配置文件开启Super权限模式:

    -Dzookeeper.DigestAuthenticationProvider.superDigest=<username>:<base64encoded(SHA1(<password>))>
    

    比如我添加一个super:super的超管:
    在这里插入图片描述

    然后我们需要修改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用户都可以直接访问。

授权对象(ID)

授权对象就是说我们要把权限赋予谁,而对应于4种不同的权限模式来说,如果我们选择采用IP方式,使用的授权对象可以是一个IP地址或IP地址段;而如果使用Digest或Super方式,则对应于一个用户名。如果是World模式,是授权系统中所有的用户(anyone)。

权限信息(Permission)

即我们可以在数据节点上执行的操作,ZooKeeper中定义了以下5中权限:

  1. 数据节点(c: create)创建权限,授予权限的对象可以在数据节点下创建子节点;
  2. 数据节点(w: write)更新权限,授予权限的对象可以更新该数据节点;
  3. 数据节点(r: read)读取权限,授予权限的对象可以读取该节点的内容以及子节点的列表信息;
  4. 数据节点(d: delete)删除权限,授予权限的对象可以删除该数据节点及其子节点;
  5. 数据节点(a: admin)管理者权限,授予权限的对象可以对该数据节点体进行ACL权限设置。

操作命令

设置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

从上面可以看到:

  1. 可以使用setAcl为已有的节点设置ACL权限控制,当节点不存在时会报错;
  2. 创建的节点不设置ACL权限控制的话,默认是’world,'anyone的权限模式,权限信息是所有的权限cdrwa;
  3. 使用getAcl查看digest模式的节点的权限信息,只能看到密文的信息;
  4. 使用addauth digest <username>:<password> 注册用户信息之后也可以使用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路径下的,如图所示:
在这里插入图片描述
从图中我们也可以看到:

  1. 事务日志的命令是以log.十六进制数的格式命名的,且大小都是统一的65537KB,因为ZooKeeper进行事务日志文件操作时会频繁进行磁盘IO操作,事务日志不断追加写操作会触发底层磁盘IO为文件开辟新的磁盘块(磁盘Seek)。为了提升磁盘 IO效率,ZooKeeper在创建事务日志时会进行文件空间的预分配,预分配的磁盘大小可以通过系统参数zookeeper.preAllocSize进行配置。
  2. 直接打开事务日志文件是看不了的,因为它是一个二进制文件。

我们想要打开事务日志文件看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

可以了解到:

  1. 事务日志不会记录get、stat、ls等查询操作。

  2. 日志中从左到右分别记录了操作时间、客户端会话ID、cxid(客户端事务id)、zxid(事务id)、操作类型、节点路径。

  3. 如果是修改节点的值,在节点路径后面从左到右记录节点数据(用#+ascii 码表示)、节点版本;

    如果是创建节点的话,在节点路径后面从左到右记录节点的权限信息,T或F代表是否是临时节点,最后面的数字则表示当前父节点的子节点版本数(创建一个节点则会自增1,节点删除后这个版本数保存不变);

    如果是修改节点权限,在节点路径后面从左到右记录节点的权限信息、节点版本。

  4. 当打开一个客户端时,cxid会从0开始,并且打印一个创建会话的日志。

  5. 当执行get命令时,cxid会自增1,而zxid不会自增,并且不会打印日志。

  6. 事务日志是有顺序的,按照操作的顺序记录数据,而且我们可以看到,事务日志的文件名的后缀就是日志中最小的那个事务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,快照数据主要是为了快速恢复,事务日志文件是每次事务请求都会进行追加的操作,而快照是达到某种设定条件下的内存全量数据。所以通常快照数据是反映当时内存数据的状态。事务日志是更全面的数据,所以恢复数据的时候,可以先恢复快照数据,再通过增量恢复事务日志中的数据即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值