zookeeper_overview

概述

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

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

zk 具有以下特性

  • 有序的
  • 高可用
  • 高吞吐
  • 低延迟

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

zookeeper_overview

zk 数据模型

znode

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

如下图所示

zookeeper_overview

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 状态。

zookeeper_overview

当我们使用以下代码连接 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 官网

Original: https://www.cnblogs.com/cyrus-s/p/15506553.html
Author: 三木同学
Title: zookeeper_overview

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/589515/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 消息推送接口设计(内含源码)

    我是3y,一年 CRUD经验用十年的 markdown程序员👨🏻‍💻常年被誉为优质八股文选手 今天要做的就是实现 austin-api和 austin-api-impl模块的部分代…

    Java 2023年6月9日
    0105
  • String vs StringBuffer vs StringBuilder

    String vs StringBuffer vs StringBuilder 本文翻译自:https://www.digitalocean.com/community/tutor…

    Java 2023年6月15日
    064
  • 超越iTerm! 号称下一代终端神器,功能贼强大!

    程序员的一生,用的最多的两个工具,一个是代码编辑器(Code Editor),另外一个就是命令行终端工具(Terminal)。这两个工具对于提高开发效率至关重要。 代码编辑器在过去…

    Java 2023年6月9日
    065
  • 多线程编程总结:二、Thread的那些坑和填坑的线程池

    在.Net的多线程编程中,最基础的一个模块类是Thread类,但是我们在实际开发中却应该少用Thread去直接创建线程。原因如下: 一、Thread.Priority 这个属性允许…

    Java 2023年5月30日
    082
  • 戏说领域驱动设计(十五)——内核元素

    前面细讲了基于CQS的4层架构,其中的领域模型层也就是六边型架构中的内核在整个开发流程中工作占比最大,也是需要工程师最需要关注地方。那么话说回来了,里面到底包含了什么东西需要投入如…

    Java 2023年6月7日
    086
  • Java 重载

    java允许一个类中含有多个同名方法,但要求各个同名方法的形参数量或类型或顺序不一致。 调用方法时按照传入的实参调用对应的方法,这就叫重载。 如果同名方法且形参一致,那就不叫重载了…

    Java 2023年6月5日
    069
  • 线程中断interrupt

    public class InterruptThread2 extends Thread{ public static void main(String[] args) { try…

    Java 2023年5月30日
    074
  • 关于Java注释报错的一个问题

    做作业时发现一个问题,注释会报错主要代码如下: String str = "C:\\users\\Default.migrated"; str += &quot…

    Java 2023年6月6日
    078
  • Java 8中Collectors.toMap空指针异常源码分析

    当需要将一个List转换为Map时,可以使用 Java 8 中的 Collectors.toMap() 方法,Map是由key-value组成的键值对集合,在使用 Collecto…

    Java 2023年6月8日
    077
  • JS 模块化- 01 模块化前传

    前端技术的发展不断融入了很多后端的思想,逐步形成前端的 “四个现代化”:工程化、模块化、规范化、流程化。这个主题介绍 模块化 ,主要内容包括模块化前传(早期…

    Java 2023年6月16日
    084
  • private static final long serialVersionUID = 1L 的作用

    1、这句话的意思是定义程序序列化ID 2、什么是序列化? Serializable,Java的一个接口,用来完成java的序列化和反序列化操作的; 任何类型只要实现了Seriali…

    Java 2023年6月5日
    085
  • 一个Golang的REPL工具

    REPL为Read-Eval-Print Loop的简写,为一种简易的,可交互式的编程环境,使用者可以方便的调试相关代码: Read: 读取用户输入;Eval: 计算输入的数据;P…

    Java 2023年6月16日
    076
  • tomcat加载启动过程

    流程图 posted @2022-08-19 17:43 默念x 阅读(8 ) 评论() 编辑 Original: https://www.cnblogs.com/monianxd…

    Java 2023年6月9日
    083
  • 队列内存限制思路防止OOM

    前几天在一个开源项目的 github 里面看到这样的一个 pr: 光是看这个名字,里面有个 MemorySafe,我就有点陷进去了。 我先给你看看这个东西: 这个肯定很眼熟吧?我是…

    Java 2023年6月13日
    082
  • java多线程如何设置优先级

    从thread类中,我们可以看到类中预先定义了三个优先级。通过getpriority可以看到新建线程的默认等级。 public class ExtendsThread { publ…

    Java 2023年5月29日
    092
  • Caffeine缓存框架入门学习

    Caffeine缓存框架入门学习和常用API 引入依赖 com.github.ben-manes.caffeine caffeine 2.5.5 基础创建方式 Cache cach…

    Java 2023年6月13日
    081
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球