Zookeeper重要理论

1、数据模型 znode

在这里插入图片描述
zk 数据存储结构与标准的 Unix 文件系统非常相似,都是在根节点下挂很多子节点。zk中没有引入传统文件系统中目录与文件的概念,而是使用了称为 znode 的数据节点概念。
znode 是 zk 中数据的最小单元,每个 znode 上都可以保存数据,同时还可以挂载子节点,形成一个树形化命名空间。

1.1、节点类型
  • 持久节点
  • 持久顺序节点
  • 临时节点:临时节点的生命周期与客户端的会话绑定在一起。临时节点不能有子节点,即临时节点只能是叶子节点
  • 临时顺序节点
1.2、节点状态
  • cZxid:Created Zxid,表示当前 znode 被创建时的事务 ID
  • ctime:Created Time,表示当前 znode 被创建的时间
  • mZxid:Modified Zxid,表示当前 znode 最后一次被修改时的事务 ID
  • mtime:Modified Time,表示当前 znode 最后一次被修改时的时间
  • pZxid:表示当前 znode 的子节点列表最后一次被修改时的事务 ID。注意,只能是其子节点列表变更了才会引起 pZxid 的变更,子节点内容的修改不会影响 pZxid
  • cversion:Children Version,表示子节点的版本号。该版本号用于充当乐观锁
  • dataVersion:表示当前 znode 数据的版本号。该版本号用于充当乐观锁
  • aclVersion:表示当前 znode 的权限 ACL 的版本号。该版本号用于充当乐观锁
  • ephemeralOwner:若当前 znode 是持久节点,则其值为 0;若为临时节点,则其值为创建该节点的会话的 SessionID。当会话消失后,会根据 SessionID 来查找与该会话相关的临时节点进行删除。
  • dataLength:当前 znode 中存放的数据的长度
  • numChildren:当前 znode 所包含的子节点的个数
1.3、会话

会话是 zk 中最重要的概念之一,客户端与服务端之间的任何交互操作都与会话相关。
ZooKeeper 客户端启动时,首先会与 zk 服务器建立一个 TCP 长连接。连接一旦建立,客户端会话的生命周期也就开始了。

1.3.1、会话状态
  • CONNECTING:连接中。客户端要创建连接,其首先会在客户端创建一个 zk 对象,代表服务器。其会采用轮询的方式对服务器列表逐个尝试连接,直到连接成功。不过,为了对 Server 进行负载均衡,其会首先对服务器列表进行打散操作,然后再轮询。
  • CONNECTED:已经连接
  • CLOSED:连接已经关闭
1.3.2、会话连接事件

客户端与服务端的长连接失效后,客户端将进行重连。在重连过程中客户端会产生三种会话连接事件:

  • CONNECTION_LOSS:连接丢失。因为网络抖动等原因导致连接中断,在客户端会引发连接丢失事件。该事件会触发客户端逐个尝试重新连接服务器,直到连接成功或超时。
  • SESSION_MOVED:会话转移。当连接丢失后,在 SessionTimeout 内重连成功,则 SessionId是不变的。若两次连接上的 Server 不是同一个,则会引发会话转移事件。该事件会引发客户端更新本地 zk 对象中的相关信息。
  • SESSION_EXPIRED:会话失效。若客户端在 SessionTimeout 内没有连接成功,则服务器会将该会话进行清除,并向Client发送通知。但在Client收到通过之前,又连接上了Server,此时的这个会话是失效的,会引发会话失效事件。该事件会触发客户端重新生成新的SesssionId 重新连接 Server。
1.3.3、会话连接超时管理–分桶策略

分桶策略是指,将超时时间相近的会话放到同一个桶中来进行管理,以减少管理的复杂度。在检查超时时,只需要检查桶中剩下的会话即可,因为没有超时的会话已经被移出了桶,而桶中存在的会话就是超时的会话。

从以下描述可知,zk 对于会话的超时管理并非是精确的管理,即并非是一超时马上就执行相关的超时操作。

分桶依据

分桶的计算依据为:

ExpirationTime = CurrentTime + SessionTimeout
BucketTime = (ExpirationTime/ExpirationInterval + 1) * ExpirationInterval

从以上公式可知,一个桶的大小为 ExpirationInterval 时间。只要 ExpirationTime 落入到同一个桶中,系统就会对其中的会话超时进行统一管理

2、ACL

ACL 全称为 Access Control List(访问控制列表),是一种细粒度的权限管理策略,可以针对任意用户与组进行细粒度的权限控制。zk 利用 ACL 控制 znode 节点的访问权限,如节点数据读写、节点创建、节点删除、读取子节点列表、设置节点权限等。

UGO,粗粒度权限管理。
目前在 Linux/Unix 文件系统中使用,也是使用最广泛的权限控制方式。是一种粗粒度的文件系统权限控制模式。

2.1、zk 的 ACL 维度

Unix/Linux 系统的 ACL 分为两个维度:组与权限,且目录的子目录或文件能够继承父目录的 ACL 的。
而 Zookeeper 的 ACL 分为三个维度:授权策略 scheme、授权对象 id、用户权限 permission,子 znode 不会继承父 znode 的权限。

授权策略
授权策略用于确定权限验证过程中使用的检验策略(即通过什么来验证权限),在 zk 中最常用的有四种策略。

  • IP:根据 IP 进行验证。
  • digest:根据用户名与密码进行验证。
  • world:对所有用户不做任何验证。
  • super:超级用户对任意节点具有任意操作权限

授权对象 id
授权对象指的是权限赋予的用户。不同的授权策略具有不同类型的授权对象。下面是各个授权模式对应的授权对象 id:

  • ip:将权限授予指定的 ip
  • digest:将权限授予具有指定用户名与密码的用户。
  • world:将权限授予一个用户 anyone
  • Super:将权限授予具有指定用户名与密码的用户。

权限 Permission
权限指的是通过验证的用户可以对 znode 执行的操作。共有五种权限,不过 zk 支持自定义权限。

  • c:Create,允许授权对象在当前节点下创建子节点
  • d:Delete,允许授权对象删除当前节点
  • r:Read,允许授权对象读取当前节点的数据内容及子节点列表
  • w:Write,允许授权对象修改当前节点数据内容及子节点列表(可以为当前节点增/删 除子节点)
  • a:Acl,允许授权对象对当前节点进行 ACL 设置
3、Watcher 机制

zk 通过 Watcher 机制实现了发布/订阅模式。

3.1、 watcher 工作原理

在这里插入图片描述

3.2、 watcher 事件

对于同一个事件类型,在不同的通知状态中代表的含义是不同的。

客户端所处状态事件类型(常量值)触发条件说明
SyncConnectedNone(-1)客户端与服务器成功建立会话此时客户端与服务器处于连接状态
NodeCreated(1)Watcher 监听的对应数据节点被创建
NodeDeleted(2)Watcher 监听的对应数据节点被删除
NodeDataChanged(3)Watcher 监听的对应数据节点的数据内容发生变化
NodeChildrenChanged(4)Watcher 监听的节点的子节点列表发生变化
Disconnected(0)None(-1)客户端与 zk 断开连接此时客户端与服务器处于连接断开状态
Expired(-112)None(-1)会话失效此时客户端会话失效,通常会收到SessionExpiredException异常
AuthFailedNone(-1)使用错误的 scheme进行权限检查通常会收到AuthFailedException 异 常
3.3、 watcher 特性

zk 的 watcher 机制具有以下几个特性。

  • 一次性:watcher 机制不适合监听变化非常频繁的场景
  • 串行性:只有当当前的 watcher 回调执行完毕了,才会向 server 注册新的 watcher(注 意,是对同一个节点相同事件类型的监听)。
  • 轻量级:Client 向 Server 发送的 watcher 不是一个完整的,而是简易版的。另外,回调逻辑不是 Server 端的,而是 Client 的。
4、客户端
4.1、 客户端命令
//连接zk服务
zkCli.sh
//连接其他zk服务
zkCli.sh -server ip
//查看子节点
ls /
//创建永久节点
create /address add
//创建顺序节点
create -s /address /wuhan wh
//创建临时节点
create -e /address /shenzhen sz
//获取持久节点数据
get /address
//获取顺序节点信息
get /address/wuhan
//获取临时节点信息
get /address/shenzhen 
//更新节点数据内容
set /address adds
//删除节点
delete /address/wuhan

ACL操作

//查看权限
getAcl /address
//设置权限
addauth digest zs:123//增加一个认证用户zs,密码123
setAcl /address auth:zs:123:cdrwa//为/adress节点指定只有zs用户才可以访问该节点,访问权限为所有权限
4.2、 ZKClient 客户端

ZkClient 是一个开源客户端,在 Zookeeper 原生 API 接口的基础上进行了包装,更便于开发人员使用。内部实现了 Session 超时重连,Watcher 反复注册等功能。像 dubbo 等框架对其也进行了集成使用。

4.2.1 API介绍
4.2.1.1 创建会话

ZkClient 中提供了以下个构造器用于创建会话:
在这里插入图片描述
参数的意义为:

参数名意义
zkServers指定 zk 服务器列表,由英文状态逗号分开的 host:port 字符串组成
connectionTimeout设置连接创建超时时间,单位毫秒。在此时间内无法创建与 zk 的连接,则直接放弃连接,并抛出异常
sessionTimeout设置会话超时时间,单位毫秒
zkSerializer为会话指定序列化器。zk 节点内容仅支持字节数组(byte[])类型,且 zk 不负责序列化。在创建 zkClient 时需要指定所要使用的序列化器,例如 Hessian 或 Kryo。默认使用 Java 自带的序列化方式进行对象的序列化。当为会话指定了序列化器后,客户端在进行读写操作时就会自动进行序列化与反序列化
connectionIZkConnection 接口对象,是对 zk 原生 API 的最直接包装,是和 zk最直接的交互层,包含了增删改查等一系列方法。该接口最常用的实现类是 zkClient 默认的实现类 ZkConnection,其可以完成绝大部分的业务需求。
operationRetryTimeout设置重试超时时间,单位毫秒
4.2.1.2 创建节点

在这里插入图片描述
参数的意义为:

参数名意义
path要创建的节点完整路径
data节点的初始数据内容,可以传入 Object 类型及 null。zk 原生 API中只允许向节点传入 byte[]数据作为数据内容,但 zkClient 中具有
自定义序列化器,所以可以传入各种类型对象。
mode节点类型,CreateMode 枚举常量,常用的有四种类型。PERSISTENT:持久型;PERSISTENT_SEQUENTIAL:持久顺序型;EPHEMERAL:临时型;EPHEMERAL_SEQUENTIAL:临时顺序型
acl节点的 ACL 策略
callback回调接口
context执行回调时可以使用的上下文对象
createParents是否级递归创建节点。zk 原生 API 中要创建的节点路径必须存在,即要创建子节点,父节点必须存在。但 zkClient 解决了这个问题,可以做递归节点创建。没有父节点,可以先自动创建了父节点,然后再在其下创建子节点
4.2.1.3 删除节点

在这里插入图片描述
参数的意义为:

参数名意义
path要删除的节点的完整路径
version要删除的节点中包含的数据版本
4.2.1.4 更新节点

在这里插入图片描述
参数的意义为:

参数名意义
path要更新的节点的完整路径
data要采用的新的数据值
expectedVersion数据更新后要采用的数据版本号
4.2.1.5 检测节点是否存在

在这里插入图片描述
参数的意义为:

参数名意义
path要判断存在性节点的完整路径
watch要判断存在性节点及其子孙节点是否具有 watcher 监听
4.2.1.6 获取节点数据内容

在这里插入图片描述
参数的意义为:

参数名意义
path要读取数据内容的节点的完整路径
watch指定节点及其子孙节点是否具有 watcher 监听
returnNullIfPathNotExists这是个 boolean 值。默认情况下若指定的节点不存在,则会抛出 KeeperException$NoNodeException 异常。设置该值为 true,若指定节点不存在,则直接返回 null 而不再抛出异常。
watch指定当前节点的状态信息。不过,执行过后该 stat 值会被最新获取到的 stat 值给替换。
4.2.1.7 获取子节点列表

在这里插入图片描述
参数的意义为:

参数名意义
path要获取子节点列表的节点的完整路径
watch要获取子节点列表的节点及其子孙节点是否具有 watcher 监听
4.2.1.8 watcher 注册

ZkClient 采用 Listener 来实现 Watcher 监听。客户端可以通过注册相关监听器来实现对zk 服务端事件的订阅。
可以通过 subscribeXxx()方法实现 watcher 注册,即相关事件订阅;通过 unsubscribeXxx()方法取消相关事件的订阅。
在这里插入图片描述

参数的意义为:

参数名意义
path要操作节点的完整路径
IZkChildListener子节点数量变化监听器
IZkDataListener数据内容变化监听器
IZkStateListener客户端与zk的会话连接状态变化监听器,可以监听新会话的创建、会话创建出错、连接状态改变。连接状态是系统定义好的枚举类型 Event.KeeperState 的常量
4.2.2 代码

依赖

	<dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.10</version>
        </dependency>
		//创建会话
        ZkClient zkClient = new ZkClient(ip);
        //指定序列化器
        zkClient.setZkSerializer(new SerializableSerializer());

        //创建节点
        String path = "/address/beijing";
        String data = "bj";
        CreateMode createMode = CreateMode.PERSISTENT;
        zkClient.create(path, data, createMode);

        //获取节点
        Object readData = zkClient.readData(path);

        //更新节点
        zkClient.writeData(path, "bj1");

        //注册watcher
        zkClient.subscribeDataChanges(path, new IZkDataListener() {
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {
                log.info("节点路径:{}", dataPath);
                log.info("数据内容更新为:{}", data);
            }

            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                log.info(dataPath + "的数据内容被删除");
            }
        });

        //判断节点是否存在
        boolean exists = zkClient.exists(path);
4.3、 Curator 客户端

Curator 是 Netflix 公司开源的一套 zk 客户端框架,与 ZkClient 一样,其也封装了 zk 原生API。其目前已经成为 Apache 的顶级项目。同时,Curator 还提供了一套易用性、可读性更强的 Fluent 风格的客户端 API 框架

4.3.1、API 介绍
4.3.1.1、创建会话

加粗样式普通 API 创建 newClient()
在 CuratorFrameworkFactory 类中提供了两个静态方法用于完成会话的创建。
在这里插入图片描述
参数的意义为:

参数名意义
path要操作节点的完整路径
connectString指定 zk 服务器列表,由英文状态逗号分开的 host:port 字符串组成
sessionTimeoutMs设置会话超时时间,单位毫秒,默认 60 秒
connectionTimeoutMs设置连接超时时间,单位毫秒,默认 15 秒
retryPolicy重试策略,内置有四种策略,分别由以下四个类的实例指定:ExponentialBackoffRetry、RetryNTimes、RetryOneTime、
RetryUntilElapsed

Fluent 风格创建

//创建客户端
CuratorFramework client= CuratorFrameworkFactory
         .builder()
         .connectString(ip)
         .sessionTimeoutMs(5000)
         .connectionTimeoutMs(3000)
         .retryPolicy(new ExponentialBackoffRetry(1000, 3))//重试策略:第一秒重试一次,最多重试3次
         .namespace("logs")
         .build();
 //开启客户端
 client.start();
4.3.1.2、节点操作
String path = "/address";
String data = "hello, Timi";
String newData = "hello, Tomas";
//创建一个节点,初始内容为空,默认为持久化节点
client.create().forPath(path);
//创建一个节点,并初始化内容,Curator 在指定数据内容时,只能使用 byte[]作为方法参数
client.create().forPath(path, data.getBytes());
//创建一个临时节点,初始内容为空, CreateMode 为枚举类型
client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
//创建一个临时节点,并自动递归创建父节点。若指定的节点多级父节点均不存在,则会自动创建
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL)
        .forPath(path);
//删除一个节点,只能将叶子节点删除,其父节点不会被删除。
client.delete().forPath(path);
//删除一个节点,并递归删除其所有子节点
client.delete().deletingChildrenIfNeeded().forPath(path);
//设置一个节点的数据内容,该方法具有返回值,返回值为 Stat 状态对象
client.setData().forPath(path, newData.getBytes());
//检测节点是否存在,返回值为 Stat 状态对象。若 stat 为 null,说明该节点不存在,否则说明节点是存在的。
Stat stat = client.checkExists().forPath(path);
//获取节点数据内容
byte[] dataContent = client.getData().forPath(path);
//获取子节点列表
List<String> childrenNames = client.getChildren().forPath(path);

//watcher 注册 usingWatcher(), curator 中绑定 watcher 的操作有三个:checkExists()、getData()、getChildren()
//监听节点的存在性变化
Stat stat1 = client.checkExists().usingWatcher((CuratorWatcher) event -> {
    log.info("节点存在性发生变化");
}).forPath(path);
//监听节点的内容变化
byte[] content = client.getData().usingWatcher((CuratorWatcher) event -> {
    System.out.println("节点数据内容发生变化");
}).forPath(path);
//监听节点子节点列表变化
List<String> sons = client.getChildren().usingWatcher((CuratorWatcher) event -> {
    System.out.println("节点的子节点列表发生变化");
}).forPath(path);
5、Zookeeper 典型应用场景
5.1 配置维护
5.1.1 介绍

分布式系统中,很多服务都是部署在集群中的,即多台服务器中部署着完全相同的应用,起着完全相同的作用。当然,集群中的这些服务器的配置文件是完全相同的。
若集群中服务器的配置文件需要进行修改,那么我们就需要逐台修改这些服务器中的配置文件,出错的概率大大提升。

5.1.2 实现原理

在这里插入图片描述
zk 可以通过“发布/订阅模型”实现对集群配置文件的管理与维护。“发布/订阅模型”分为推模式(Push)与拉模式(Pull)。zk 的“发布/订阅模型”采用的是推拉相结合的模式。

5.2 命名服务
5.2.1 介绍

命名服务是指可以为一定范围内的元素命名一个唯一标识,以与其它元素进行区分。在分布式系统中被命名的实体可以是集群中的主机、服务地址等。

5.2.2 实现原理

在这里插入图片描述
通过利用 zk 中顺序节点自动生成唯一编号的特点来实现命名服务。
首先创建一组业务相关的节点,然后再在这些节点下再创建顺序节点,此时的顺序节点的路径加名称即为生成的唯一标识。

5.3 DNS 服务
5.3.1 介绍

zk 的 DNS 服务是命名服务的一种特殊用法。其对外表现出的功能主要是防止提供者的单点问题,实现对提供者的负载均衡。

5.3.2 实现原理

在这里插入图片描述

5.3.3 什么是 DNS

DNS,Domain Name System,域名系统,即可以将一个名称与特定的主机 IP 加端口号进行绑定。zk 可以充当 DNS 的作用,完成域名到主机的映射。

5.3.4 基本 DNS 实现原理

假设应用程序 app1 与 app2 分别用于提供 service1 与 service2 两种服务,现要将其注册到 zk 中,具体的实现步骤如下图所示。
在这里插入图片描述
若某应用(例如 app1)具有多个服务名称,则可以在该应用节点下添加多个子节点
若某域名下的服务提供者增加,则可以首先读取该节点中的数据内容,然后再将该增加的提 供者主机信息与原来数据内容一起再写入到节点数据内容中。 若某域名需要修改,则则直接新增一个节点

5.3.5 具有状态收集功能的 DNS 实现原理

在这里插入图片描述
以上模型存在一个问题,如何获取各个提供者主机的健康状态、运行状态呢?可以为每一个域名节点再添加一个状态子节点,而该状态子节点的数据内容则为开发人员定义好的状态数据。这些状态数据是如何获取到的呢?是通过状态收集器(开发人员自行开发的)定期写入到 zk 的该节点中的。
阿里的 Dubbo 就是使用 Zookeeper 作为域名服务器的

5.4 Master 选举

集群是分布式系统中不可或缺的组成部分,是为了解决分布式系统中计算单元的单点问题,水平扩展计算单元的处理能力的一种解决方案。
一般情况下,会在群集中选举出一个 Master,用于协调集群中的其它 Slave 主机,对于Slave 主机的状态具有决定权。
例如,读写分离集群,Master 处理写请求,Slave 处理读请求(主从集群)再如,对于复杂处理逻辑的系统,Master 负责处理复杂逻辑计算,然后将计算结果写入到一个中间存储系统(DB,或 DFS 等),Slave 负责从中间存储系统中读取结果,并向读请求进行响应。

5.5 分布式同步

分布式同步,也称为分布式协调,是分布式系统中不可缺少的环节,是将不同的分布式组件有机结合起来的关键。对于一个在多台机器上运行的应用而言,通常需要一个协调者来控制整个系统的运行流程,例如执行的先后顺序,或执行与不执行等。

5.6 集群管理

能够随时获取到以下信息 当前集群中各个主机的运行时状态、 当前集群中主机的存活状况:
在这里插入图片描述

5.7 分布式锁

分布式锁是控制分布式系统同步访问共享资源的一种方式。Zookeeper 可以实现分布式锁功能。根据用户操作类型的不同,可以分为排他锁与共享锁。

5.7.1 分布式锁的实现

在 zk 上对于分布式锁的实现,使用的是类似于“/xs_lock/[hostname]-请求类型-序号”的临时顺序节点。
在这里插入图片描述
其具体实现过程如下:

  • Step1:每一个客户端会对 xs_lock 节点注册子节点列表变更事件的 watcher 监听,随时监听子节点的变化情况
  • Step2:若客户端需要向某资源发出操作请求时,其首先要从 zk 中获取一把分布式锁。
    这个获取分布式锁的过程即是到 xs_lock 节点下创建一个读写操作的临时顺序节点。读写操作的顺序性就是通过这些子节点的顺序性体现的。
  • Step3:在当前子节点创建完后,即 zk 将分布式锁分配给该客户端后,当前子节点会对 比其与其它子节点序号的大小关系,并根据读写操作的不同,执行不同的逻辑。
    • 读请求:若没有比自己小的节点,或比自己小的节点都是读请求节点,则当前请求可以直接读取;若比自己小的节点中存在写请求节点,则当前请求等待。
    • 写请求:若没有比自己小的节点,则直接进行写操作。若发现有比自己小的节 点,那些节点无论是读还是写节点,当前写操作都需要等待。
  • Step4:客户端操作完毕后,与 zk 的连接断开,则 zk 中该会话对应的节点消失。
5.7.2 分布式锁的改进

前面的实现方式存在“羊群效应”,为了解决其所带来的性能下降,可以对前述分布式锁的实现进行改进。
由于一个操作而引发了大量的低效或无用的操作的执行,这种情况称为羊群效应。
当客户端请求发出后,在 zk 中创建相应的临时顺序节点后马上获取当前的/xs_lock 的所有子节点列表,但任何客户端都不向/xs_lock 注册用于监听子节点列表变化的 watcher。而是改为根据请求类型的不同向“对其有影响的”子节点注册 watcher。

5.8 分布式队列

常用的分布式消息队列中间件产品有:RabbitMQ、Kafka。zk 也可以实现简单的消息队列。

5.8.1 FIFO 队列

在这里插入图片描述
zk 实现 FIFO 队列的思路是:利用顺序节点的有序性,为每个数据在 zk 中都创建一个相应的节点。然后为每个节点都注册 watcher 监听。一个节点被消费,则会引发消费者消费下一个节点,直到消费完毕。

5.8.2 分布式屏障 Barrier 队列

在这里插入图片描述
Barrier,屏障、障碍物。Barrier 队列是分布式系统中的一种同步协调器,规定了一个队列中的元素必须全部聚齐后才能继续执行后面的任务,否则一直等待。其常见于大规模分布式并行计算的应用场景中:最终的合并计算需要基于很多并行计算的子结果来进行。
zk 对于 Barrier 的实现原理是,在 zk 中创建一个/barrier 节点,其数据内容设置为屏障打开的阈值,即当其下的子节点数量达到该阈值后,app 才可进行最终的计算,否则一直等待。每一个并行运算完成,都会在/barrier 下创建一个子节点,直到所有并行运算完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值