zookeeper_overview

概述

zk 是一个开源的,分布式协调服务,它的目的就是为了服务于分布式应用。zk 允许分布式应用通过 zk 的节点进行相互协调,常见的有配置同步、分布式锁、微服务注册与发现等等。

zk 本身和它所要协调的分布式应用一样,也是也是在集群中相互复制,以保证 zk 的高可用性。每台服务器都需要相互了解,数据保持一致。只要大多数服务器是可用的,那么整个 zk 集群就是可用的。

zk 具有以下特性

  • 有序的

  • 高可用

  • 高吞吐

  • 低延迟

其中,zk 使用一个 zxid(事务id)来保证每个更新的先后顺序,客户端可以用这个特性来实现同步原语,也就是分布式锁,zk 的高可用是通过 zk 集群的数据一致性来保证的。高吞吐以及低延迟是因为 zk 在内存中维护一套数据映射,通过内存进行读取更新。当然 zk 还会将日志文件以及数据快照持久化,这也同样为高可用提供了一定的支持。

img

zk 数据模型

znode

zk 中的每个节点都由路径标识,和标准文件系统类似,都是采用 "/" 反斜杠进行进行路径分割。

如下图所示

img

znode 是我们访问的主要实体,我们需要对它有一个清晰的认识。

zk 中的每个节点都可以拥有子节点以及相关联的数据。这里的数据一般是存储分布式服务的协调数据如:状态信息、配置信息、位置信息等,因此存储在节点上的数据通常很小,在字节到千字节的范围内。

watch(监听)

zk 支持可以在指定的节点上设置监听,当节点更改后,监听会被触发以及删除,客户端会受到一个回调包,告知节点被更改了。

3.6.0 版本支持持久化监听,监听触发后可以不被删除。

数据权限(ACL)

zk 的每个节点都会有一个 acl 列表(访问控制列表)来限制谁可以做什么

临时节点

ZooKeeper 也有临时节点的概念。只要创建znode的会话是存活的,这些 znode 就会存在。当会话结束时,删除znode。由于这种行为,临时节点不允许有子节点。会话的临时列表可以使用 getEphemerals() api 检索。

序列节点--唯一命名

当创建znode时,你也可以请求ZooKeeper在路径的末尾添加一个单调递增的计数器。这个计数器对于父节点是唯一的。如创建了一个节点 /A,在 A 节点使用唯一命名,那么后续创建子节点就会用数字自动递增,如:/A/1, /A/2。这个计数器是靠父节点维护的,这里是 A 节点。

zk 中的时间

  • zxid:zk 事务 id,每次对 zk状态信息的一个更改都会收到一个事务 id,该 id 是唯一的,zk 会保证事务 id 小的在事务 id 大的之前进行更新,防止出现丢失更新的情况。

  • 版本号:对节点的每次修改都会使得版本号增加,客户端操作一个节点时会带上版本号,如果版本号不一致,那么就会操作失败。这相当于加锁了。

    • version:修改 znode 数据的次数
    • cversion:对 znode 子节点修改的次数
    • aversion:对 znode 的 acl 列表修改的次数
  • Ticks:配置文件中的一个时间,单位是毫米,zk 中的大部分时间都是以该时间为基本单位。比如会话超时,就是 2 Ticks。

  • Real time:zk 除了在创建和修改 znode 时会将时间戳放入节点的 stat 结构中,其他任何地方都不会使用现实时间。

stat 结构

zk 节点的信息由一个 stat 结构维护

包含了以下信息:

  • czxid:该节点创建时候的 zxid

  • mzxid:该节点最后变更时候的 zxid

  • pzxid:该节点的子节点最后变更时候的 zxid

  • ctime :节点创建时候的时间戳

  • mtime:节点修改时候的时间戳

  • version :znode 数据变更的次数

  • cversion :znode 子节点变更的次数

  • aversion : znode 的 acl 列表变更的次数

  • ephemeralOwner:如果该节点是一个临时节点,则为创建该节点的会话id。如果它不是一个临时节点,它将为0。

  • dataLength :该节点存储的数据的长度

  • numChildren:该节点的子节点数

会话(session)

当我们使用客户端连接 zk 服务端的时候就创建了一个会话,在建立连接的过程中,会话状态时 connecting,当通过验证,连接成功的时候,状态进入 connected,如果因为身份验证失败或者会话超时,那么,就进入一个 close 状态。

img

当我们使用以下代码连接 zk 服务器成功的时候就是建立了一个会话。

ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 4000, new Watcher() {
    @Override
    public void process(WatchedEvent watchedEvent) {
        if (Event.KeeperState.SyncConnected == watchedEvent.getState()) {
            System.out.println("watch");
        }
    }
});

zk 服务器会给客户端分配一个 64 位的 session id,同时为了安全考虑,也会配套的创建一套加密密码,当客户端因某种原因。连接到其他的 zk 服务器的时候,需要将 session id 和密码一起发送给 zk 服务器,重新建立连接。

zk 有一个 session 过期时间,默认为 2 Ticks time,当超过这个时间,zk 服务器没有收到客户端的信息(包括心跳),那么就会断开连接,session 就会进入到一个 close 状态。这时候在该会话中建立的所有临时节点都会被删除,同时通知给所有监听了这些节点的客户端。

如果因为连接的 zk 服务器宕机了,或者 session 在 zk 集群中重新分区时,这时候需要与其他 zk 服务器建立 session 连接,如果在超时时间内连接上了,那么状态重新回归 connected,否则,连接过期,这时候 zk 客户端会自动处理重新连接,无需重新创建新的会话对象(new ZooKeeper() )。

会话通过客户端发送的请求保持活动。如果会话在一段时间内处于空闲状态,该会话将超时,那么客户机将发送一个PING请求以保持会话处于活动状态。这个PING请求不仅允许ZooKeeper服务器知道客户端仍然是活动的,而且它还允许客户端验证它到ZooKeeper服务器的连接仍然是活动的。PING的时间足够保守,以确保有合理的时间检测死连接并重新连接到新服务器。

监听(watch)

定义:在 zk 中监听是一次性的,当对某个节点设置了监听,那么当该节点进行了变更后,客户端就会受到一个回调通知。

所有对节点的读操作都可以设置对该节点的监听:getData(), getChildren(), 以及 exists()

zooKeeper.getData("/", new Watcher() {
    @Override
    public void process(WatchedEvent event) {
        System.out.println(event.getState());
    }
}, new Stat());

zooKeeper.getChildren("/", new Watcher() {
    @Override
    public void process(WatchedEvent event) {

    }
});

zooKeeper.exists("/", new Watcher() {
    @Override
    public void process(WatchedEvent event) {

    }
});

在 zk 3.6.0 版本中,客户端还可以在znode上设置永久的、递归的监听,这些监听在被触发时不会被删除,并递归地触发注册znode以及任何子znode上的更改。

如下,分别是创建持久监听已经持久递归监听

zooKeeper.addWatch("/",AddWatchMode.PERSISTENT);
zooKeeper.addWatch("/",AddWatchMode.PERSISTENT_RECURSIVE);

监听的一些顺序性问题:

  • 客户端在获取到节点的新数据之前,会先拿到对于该节点的监听时间。
  • 监听的顺序和 zk 更新节点的顺序是一致的

一些要注意的点:

  • 标准的监听只触发一次,触发后如果想要对对应的节点继续监听数据,需要再次对该节点添加监听机制
  • 由于标准的监听是一次性的,在获取数据和发送新的监听请求这中间可能可能有多次节点变更,这样会丢失掉一些关于该节点的更新监听

访问控制列表(ACL)

acl 支持以下几种权限

  • CREATE:可以创建子节点

  • READ:可以从节点中获取数据和子节点列表

  • WRITE:可以设置节点的数据

  • DELETE:可以删除子节点

  • ADMIN:可以设置权限

保证

zk 为了能够构建更加复杂的服务,提供了以下保证

  • 顺序一致性:来自客户端的更新将按照发送的顺序执行

  • 原子性:更新要么成功,要么失败,没有部分成功部分失败

  • 单一系统映像:客户端在不同的 zk 服务器中看到的都是相同的视图。即使因为故障转移到其他服务器,也不会看到历史视图。

  • 可靠性:一旦节点被创建或更新,那么它将一直存在,除非它被删除或者更改了。

  • 及时性:保证客户端在一定时间内看到到最近视图

简单的 api

zk 立志于提供一套简单编程接口,因此只支持以下几种 api

  • create :创建节点

  • delete :删除节点

  • exists :判断某个节点是否存在

  • get data:读取某个节点的数据

  • set data:为某个节点写入数据

  • get children:检索节点的子节点列表

  • sync :等待数据被同步

实现

每台 zk 服务器都会复制将自己的数据复制一份存为副本。复制的信息包含整个 zk 的内存数据,更新数据被记录到磁盘以实现可恢复性,写入数据在写入内存前会先被序列化到磁盘。

zk 集群中的服务器分为 1 台 leader 和多台 follower。zk 集群中的每台服务器都为客户端提供服务,不同的是 follower 提供读服务,leader 提供读服务和写服务,当 follower 接收到写请求时会转发到 leader 服务器处理,leader 服务器写入数据完后会广播给所有的 follower 进行同步写数据。假如 leader 出现故障,那么会从 follower 中选举一个新 leader 出来。

文章为本人学习过程中的一些个人见解,漏洞是必不可少的,希望各位大佬多多指教,帮忙修复修复漏洞!!!

参考资料

zk 官网

posted @ 2021-11-04 07:34  三木同学  阅读(198)  评论(0编辑  收藏  举报