ZooKeeper 是 Apache 软件基金会的一个顶级项目,为分布式应用提供高效、高可用的分布式协调服务;提供配置维护、域名服务、分布式同步、组服务等功能。

1.数据结构

ZooKeeper的数据结构和文件系统非常相似,每个节点叫做znode。每个节点可以通过路径来标识,可以增加、删除 znode,以及在一个znode下增加、删除子znode。结构图如下:

seb-zk-data-structure

znode类型:

  1. PERSISTENT 持久化节点
    客户端与zookeeper断开连接后,该节点依旧存在。
  2. PERSISTENT_SEQUENTIAL 持久化有序节点
    和PERSISTENT节点类似,唯一区别是节点名会被追加一个10位数字的序列号,当序列号大于2^32-1会溢出。
  3. EPHEMERAL 临时节点
    客户端与zookeeper断开连接后,该节点被删除。
  4. EPHEMERAL_SEQUENTIAL 临时有序节点
    和EPHEMERAL节点类似,且会有序列号。
  5. CONTAINER 容器节点
    如果Container节点下面没有子节点,则Container节点在未来会被Zookeeper自动清除,定时任务默认60s 检查一次。
  6. PERSISTENT_WITH_TTL 带过期时间的持久化节点(默认禁用)
    跟PERSISTENT节点类似,但在过期时间内节点没有任何修改,并且没有任何子节点,则该节点会被删除。TTL时间单位为毫秒。
  7. PERSISTENT_SEQUENTIAL_WITH_TTL 带过期时间的持久化有序节点(默认禁用)
    跟PERSISTENT_WITH_TTL节点类似

创建(持久化)节点命令:

create /node-test-01 abc

创建(持久化)有序节点:

create /seq‐node
create -s /seq‐node/ data

创建临时节点:

create ‐e /no de-ephemeral abc

查看节点数据:

get /node-test-01

修改节点数据:

set /node-test-01 efg

查看子节点:

ls /node-test-01

查看节点状态信息:

stat /node-test-01
cZxid = 0x2
ctime = Mon Nov 15 08:38:46 PST 2021
mZxid = 0x4f
mtime = Sat Nov 20 04:58:31 PST 2021
pZxid = 0x52
cversion = 2
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 2

状态信息说明:

  • aclVersion:表示对此znode的acl版本。
  • ephemeralOwner:znode是临时znode时,表示znode所有者的 session ID。 如果znode不是临时znode,则该字段为零。
  • dataLength:znode数据字段的长度。
  • cZxid:创建znode的事务ID(Zxid的值)。
  • mZxid:最后修改znode的事务ID。
  • pZxid:最后添加或删除子节点的事务ID(子节点列表发生变化才会发生改变)。
  • ctime:znode创建时间。
  • mtime:znode最近修改时间。
  • dataVersion:znode的当前数据版本。
  • cversion:znode的子节点结果集版本
  • numChildren:znode的子znode的数量。

内存结构

Zookeeper 数据都是存储在内存(DataTree)中的,通过 NodeHashMap (ConcurrentHashMap)以路径为 key,节点为value存储。

public class DataTree {
    /**
     * This map provides a fast lookup to the datanodes. The tree is the
     * source of truth and is where all the locking occurs
     */
    private final NodeHashMap nodes;
}
/**
 * a simple wrapper to ConcurrentHashMap that recalculates a digest after
 * each mutation.
 */
public class NodeHashMapImpl implements NodeHashMap {

    private final ConcurrentHashMap<String, DataNode> nodes;
    private final boolean digestEnabled;
    private final DigestCalculator digestCalculator;
}    

DataNode 是 Zookeeper 存储节点数据的最小单位,包含了数据、权限、状态信息、子节点等数据。

/**
 * This class contains the data for a node in the data tree.
 * <p>
 * A data node contains a reference to its parent, a byte array as its data, an
 * array of ACLs, a stat object, and a set of its children's paths.
 *
 */
@SuppressFBWarnings({"EI_EXPOSE_REP", "EI_EXPOSE_REP2"})
public class DataNode implements Record {

    // the digest value of this node, calculated from path, data and stat
    private volatile long digest;

    // indicate if the digest of this node is up to date or not, used to
    // optimize the performance.
    volatile boolean digestCached;

    /** the data for this datanode */
    byte[] data;

    /**
     * the acl map long for this datanode. the datatree has the map
     */
    Long acl;

    /**
     * the stat for this node that is persisted to disk.
     */
    public StatPersisted stat;

    /**
     * the list of children for this node. note that the list of children string
     * does not contain the parent path -- just the last part of the path. This
     * should be synchronized on except deserializing (for speed up issues).
     */
    private Set<String> children = null;

2.监听机制(watch)

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

  1. 如果注册的是对某个节点数据的监听,则当这个节点被删除,或者节点数据被修改,对应的客户端将被通知:

    get -w /node-test-01

    监听端收到的通知:

    WatchedEvent state:SyncConnected type:NodeDataChanged path:/node-test-01
  2. 如果注册的是对某个节点目录的监听,则当这个目录有子节点被创建,或者有子节点被删除,对应的客户端将被通知:

    ls -w /node-test-01
  3. 如果注册的是对某个目录的递归子节点进行监听,则当这个目录下面的任意子节点有子节点被创建,或被删除;或者根节点有数据变化时,对应的客户端将被通知:

    ls -wR /node-test-01

注意:所有的通知都是一次性的(one-time trigger)

Zookeeper 事件类型:

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

3.数据持久化

Zookeeper 中的数据都会被持久化到硬盘中。

事务日志

针对每一次客户端的事务操作,Zookeeper 都会将它们记录到事务日志中;Zookeeper也会将数据变更应用到内存数据库中。我们可以在 zookeeper 的主配置文件 zoo.cfg 中配置的数据持久化目录(事务日志的存储路径)dataLogDir 。如果没有配置 dataLogDir ,事务日志将存储到 dataDir 目录。

可以使用 zookeeper 提供的格式化工具查看事务日志数据:

java -classpath lib/slf4j-api-1.7.25.jar:lib/zookeeper-3.6.3.jar:lib/zookeeper-jute-3.6.3.jar org.apache.zookeeper.server.LogFormatter /usr/local/zookeeper/apache-zookeeper-3.6.3-bin/data/version-2/log.4b

日志结果:

ZooKeeper Transactional Log File with dbid 0 txnlog format version 2
2021/11/19 PST 下午7:33:52 session 0x100004dcff80000 cxid 0x0 zxid 0x4b createSession 30000
 2,3769449293

2021/11/19 PST 下午7:34:09 session 0x10000192e590000 cxid 0x0 zxid 0x4c closeSession v{}
 2,3769449293

2021/11/20 PST 上午4:56:53 session 0x100004dcff80001 cxid 0x0 zxid 0x4d createSession 30000
 2,3769449293

2021/11/20 PST 上午4:58:15 session 0x100004dcff80001 cxid 0x5 zxid 0x4e create '/node-test-01/abc,,v{s{31,s{'world,'anyone}}},F,1
 2,8164596078

2021/11/20 PST 上午4:58:31 session 0x100004dcff80001 cxid 0x7 zxid 0x4f setData '/node-test-01,#61626364,1
 2,5409280852

每条日志包含了操作时间、客户端会话ID、CXID、ZXID、操作类型、节点路径、节点数据(用 #+ascii 码表示)、节点版本。

数据快照

数据快照用于记录 zookeeper 上某一时刻的全量数据,并将其写入到指定的磁盘文件中。 可以通过配置 snapCount 配置每间隔事务请求个数,生成快照,数据存储在 dataDir 指定的目录中,可以通过如下方式进行查看快照数据( 为了避免集群中所有机器在同一时间进行快照,实际的快照生成时机为事务数达到 [snapCount/2 + 随机数(随机数范围为1 ~ snapCount/2 )] 个数时开始快照)

java -classpath lib/slf4j-api-1.7.25.jar:lib/snappy-java-1.1.7.jar:lib/zookeeper-3.6.3.jar:lib/zookeeper-jute-3.6.3.jar org.apache.zookeeper.server.SnapshotFormatter /usr/local/zookeeper/apache-zookeeper-3.6.3-bin/data/version-2/snapshot.2

日志结果:

ZNode Details (count=6):
----
/
  cZxid = 0x00000000000000
  ctime = Wed Dec 31 16:00:00 PST 1969
  mZxid = 0x00000000000000
  mtime = Wed Dec 31 16:00:00 PST 1969
  pZxid = 0x00000000000002
  cversion = 1
  dataVersion = 0
  aclVersion = 0
  ephemeralOwner = 0x00000000000000
  dataLength = 0
----
/node-test-01
  cZxid = 0x00000000000002
  ctime = Mon Nov 15 08:38:46 PST 2021
  mZxid = 0x00000000000002
  mtime = Mon Nov 15 08:38:46 PST 2021
  pZxid = 0x00000000000002
  cversion = 0
  dataVersion = 0
  aclVersion = 0
  ephemeralOwner = 0x00000000000000
  dataLength = 3
----
/zookeeper
  cZxid = 0x00000000000000
  ctime = Wed Dec 31 16:00:00 PST 1969
  mZxid = 0x00000000000000
  mtime = Wed Dec 31 16:00:00 PST 1969
  pZxid = 0x00000000000000
  cversion = 0
  dataVersion = 0
  aclVersion = 0
  ephemeralOwner = 0x00000000000000
  dataLength = 0
----
/zookeeper/config
  cZxid = 0x00000000000000
  ctime = Wed Dec 31 16:00:00 PST 1969
  mZxid = 0x00000000000000
  mtime = Wed Dec 31 16:00:00 PST 1969
  pZxid = 0x00000000000000
  cversion = 0
  dataVersion = 0
  aclVersion = -1
  ephemeralOwner = 0x00000000000000
  dataLength = 0
----
/zookeeper/quota
  cZxid = 0x00000000000000
  ctime = Wed Dec 31 16:00:00 PST 1969
  mZxid = 0x00000000000000
  mtime = Wed Dec 31 16:00:00 PST 1969
  pZxid = 0x00000000000000
  cversion = 0
  dataVersion = 0
  aclVersion = 0
  ephemeralOwner = 0x00000000000000
  dataLength = 0
----
Session Details (sid, timeout, ephemeralCount):
0x100004c57c30000, 30000, 0
----
Last zxid: 0x2

快照数据主要是为了快速恢复,事务日志文件是每次事务请求都会进行追加的操作,而快照是达到某种设定条件下的内存全量数据。所以通常快照数据是反应当时内存数据的状态。事务日志是更全面的数据,所以恢复数据的时候,可以先恢复快照数据,再通过增量恢复事务日志中的数据即可。

最后修改:2022 年 11 月 15 日
如果觉得我的文章对你有用,请随意赞赏