侧边栏壁纸
  • 累计撰写 98 篇文章
  • 累计创建 20 个标签
  • 累计收到 3 条评论

Zookeeper学习笔记一

林贤钦
2020-12-02 / 0 评论 / 0 点赞 / 635 阅读 / 11,057 字
温馨提示:
本文最后更新于 2021-03-03,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Zookeeper学习笔记一

一、Zookeeper概述

Zookeeper简介

Zookeeper是一个分布式应用程序协调服务,主要负责集中维护配置信息的服务,提供分布式的同步机制。其所有的服务都是做为其他分布式应用的基础。Zookeeper是Google Chubby的一个开源实现,是现在很多分布式应用的重要组件,包括诸如Hadoop、HBase、Kafka等,可以说现在Zookeeper是大家必须掌握的一门软件。

Zookeeper主要提供以下几种服务:

  • 命名服务
  • 配置管理
  • 集群管理
  • 分布式锁
  • 队列管理
  • 生成分布式唯一ID

Zookeeper的特点:

  • 简单:Zookeeper的核心是一个精简的文件系统,它支持一些简单的操作和一些抽象操作,例如,排序和通知。
  • 丰富:Zookeeper的原语操作是很丰富的,可实现一些协调数据结构和协议。例如,分布式队列、分布式锁和一组同级别节点中的“领导者选举”。
  • 高可用:Zookeeper支持集群模式,可以很容易的解决单点故障问题。
  • 松耦合式交互:不同进程间的交互不需要了解彼此,甚至可以不必同时存在,某进程在zookeeper中留下消息后,该进程结束后其它进程还可以读这条消息。
  • 资源库:Zookeeper实现了一个关于通用协调模式的开源共享存储库,能使开发者免于编写这类通用协议。

Zookeeper的设计目标:

zooKeeper致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务

  1. 高性能
    • zookeeper将全量数据存储在内存中,并直接服务于客户端的所有非事务请求,尤其用于以读为主的应用场景
  2. 高可用
    • zookeeper一般以集群的方式对外提供服务,一般3~5台机器就可以组成一个可用的 Zookeeper集群了,每台机器都会在内存中维护当前的服务器状态,井且每台机器之间都相互保持着通信。只要集群中超过一半的机器都能够正常工作,那么整个集群就能够正常对外服务
  3. 严格顺序访问
    • 对于来自客户端的每个更新请求,Zookeeper都会分配一个全局唯一的递增编号,这个编号反应了所有事务操作的先后顺序

二、Zookeeper数据模型

Zookeeper拥有一个层次的命名空间,和linux系统的文件系统非常相似,zookeeper都是采用树形层次结构,Zookeeper树中的每个节点成为Znode,每个节点可以拥有子节点,且每个节点存放一些数据。

image-20201202155938098

(1)引用方式

Znode用过路径引用,如果linux,unix中的文件路径。路径是绝对的,必须以斜杠字符开头,除此之外,他们也必须是唯一的,也就是说每个路径只有一个表示,因此这些路径不能被改变

(2)Znode结构

Zookeeper命名空间中的Znode,兼具文件和目录两种特点。即像文件一样维护着数据,元信息,ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分。图中的每个节点都包含以下数据:

  • data:Znode存储的数据信息。
  • ACL:记录Znode的访问权限,即哪些人或哪些IP可以访问本节点。
  • stat:包含Znode的各种元数据,比如事务ID、版本号、时间戳、大小等等。
  • child:当前节点的子节点引用,类似于二叉树的左孩子右孩子。

详细的节点属性

属性描述
cZxid数据结点创建时的事务ID——针对于zookeeper数据结点的管理:我们对结点数据的一些写操作都会导致zookeeper自动地为我们去开启一个事务,并且自动地去为每一个事务维护一个事务ID
ctime数据结点创建时的时间
mZxid数据结点最后一次更新时的事务ID
mtime数据结点最后一次更新时的时间
pZxid数据节点最后一次修改此znode子节点更改的zxid
cversion子结点的更改次数
dataVersion结点数据的更改次数
aclVersion结点的ACL更改次数——类似linux的权限列表,维护的是当前结点的权限列表被修改的次数
ephemeralOwner如果结点是临时结点,则表示创建该结点的会话的SessionID;如果是持久结点,该属性值为0
dataLength数据内容的长度
numChildren数据结点当前的子结点个数

(3)数据访问

Zookeeper中的每个节点存储的数据都要被原子性的操作。也就是说读操作将获取与节点相关的所有数据,写操作也将替换节点的所有数据。另外每个节点都拥有自己的ACL(访问权限列表),这个列表规定了用户的权限,即规定了特定的用户对目标节点可以执行的操作。

ACL 权限控制,使用:scheme:id:perm 来标识,主要涵盖 3 个方面:

  • 权限模式(Scheme):授权的策略
  • 授权对象(ID):授权的对象
  • 权限(Permission):授予的权限

1. scheme 采用何种方式授权

方案描述
world只有一个用户:anyone,代表登录zookeeper所有人(默认)
ip对客户端使用IP地址认证
auth使用已添加认证的用户认证
digest使用"用户名:密码"方式认证

2. ID 给谁授予权限

3. permission 授予什么权限

权限ACL简写描述
createc可以创建子结点
deleted可以删除子结点(仅下一级结点)
readr可以读取结点数据以及显示子结点列表
writew可以设置结点数据
admina可以设置结点访问控制权限列表

(4)节点类型

Zookeeper中的节点又分为临时节点持久化节点的类型在创建时被确定,并且不能改变

  • 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话( Session)结束,临时节点将被自动删除,当然可以也可以手动删除。虽然每个临时的 Znode都会绑定到一个客户端会话,但他们对所有的客户端还是可见的。另外,Zookeeper的临时节点不允许拥有子节点
  • 持久化节点:该结点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,它们才能被删除

(5)顺序节点

当创建Znode的时候,用户可以请求在Zookeeper的路径结尾添加一个递增的计数。这个计数对于此节点的父结点来说是唯一的,它的格式为“%10d”(10位数字,没有数值的数位用0补充,比如“0000000001”),当计数值大于232 -1时,计数器会溢出

(6)观察

客户端可以在节点上设置watch,我们成为监视器。当节点发生变化时(Znode的增、删、改)将会触发watch所对应的操作。当watch被触发时,Zookeeper将会向客户端发送且仅发送一条通知,因为watch只触发一次

三、Zookeeper命令的简单操作步骤

(1) 使用ls命令查看当前Zookeeper中所包含的内容:ls /

(2) 创建一个新的Znode节点"aa",以及和它相关字符,执行命令:create /aa "my first zk",默认是不带编号的

  • 创建带编号的持久性节点"bb":create -s /bb "bb"
  • 创建不带编号的临时节点"cc": create -e /cc "cc"
  • 创建带编号的临时节点"dd": create -s -e /dd "dd"

(3)使用get命令来我们创建的字符串,执行命令:get /aa

(4)接下来通过set命令来对zk所关联的字符串进行设置,执行命令:set /aa haha123

(5)将刚才创建的Znode删除,执行命令:delete /aa

(6)查看一个文件的状态信息: stat /a

四、Watch触发器

watcher概念

  • zookeeper提供了数据的发布/订阅功能,多个订阅者可同时监听某一特定主题对象,当该主题对象的自身状态发生变化时例如节点内容改变、节点下的子节点列表改变等,会实时、主动通知所有订阅者
  • zookeeper采用了 Watcher机制实现数据的发布订阅功能。该机制在被订阅对象发生变化时会异步通知客户端,因此客户端不必在 Watcher注册后轮询阻塞,从而减轻了客户端压力
  • watcher机制事件上与观察者模式类似,也可看作是一种观察者模式在分布式场景下的实现方式

watcher实现由三个部分组成

  • zookeeper服务端
  • zookeeper客户端
  • 客户端的ZKWatchManager对象

客户端首先将 Watcher注册到服务端,同时将 Watcher对象保存到客户端的watch管理器中。当Zookeeper服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的 Watch管理器会**触发相关 Watcher**来回调相应处理逻辑,从而完成整体的数据 发布/订阅流程

watcher特性

特性说明
一次性watcher一次性的,一旦被触发就会移除,再次使用时需要重新注册
客户端顺序回调watcher回调是顺序串行执行的,只有回调后客户端才能看到最新的数据状态。一个watcher回调逻辑不应该太多,以免影响别的watcher执行
轻量级WatchEvent是最小的通信单位,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容
时效性watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接收到通知;

Watcher通知状态(KeeperState)

KeeperState是客户端与服务端连接状态发生变化时对应的通知类型。路径为org.apache.zookeeper.Watcher.EventKeeperState,是一个枚举类,其枚举属性如下

枚举属性说明
SyncConnected客户端与服务器正常连接时
Disconnected客户端与服务器断开连接时
Expired会话session失效时
AuthFailed身份认证失败时

Watcher事件类型(EventType)

EventType数据节点znode发生变化时对应的通知类型。EventType变化时KeeperState永远处于SyncConnected通知状态下;当keeperState发生变化时,EventType永远为None。其路径为org.apache.zookeeper.Watcher.Event.EventType,是一个枚举类,枚举属性如下:

枚举属性说明
None
NodeCreatedWatcher监听的数据节点被创建时
NodeDeletedWatcher监听的数据节点被删除时
NodeDataChangedWatcher监听的数据节点内容发生更改时(无论数据是否真的变化)
NodeChildrenChangedWatcher监听的数据节点的子节点列表发生变更时

注意:客户端接收到的相关事件通知中只包含状态以及类型等信息,不包含节点变化前后的具体内容,变化前的数据需业务自身存储,变化后的数据需要调用get等方法重新获取

注册watcher的方法

客户端与服务器端的连接状态

  • KeeperState:通知状态

  • SyncConnected:客户端与服务器正常连接时

  • Disconnected:客户端与服务器断开连接时

  • Expired:会话session失效时

  • AuthFailed:身份认证失败时

  • 事件类型为:None

案例:

public class ZkConnectionWatcher implements Watcher {
    @Override
    public void process(WatchedEvent watchedEvent) {
        Event.KeeperState state = watchedEvent.getState();
        if(state == Event.KeeperState.SyncConnected){
            // 正常
            System.out.println("正常连接");
        }else if (state == Event.KeeperState.Disconnected){
            // 可以用Windows断开虚拟机网卡的方式模拟
            // 当会话断开会出现,断开连接不代表不能重连,在会话超时时间内重连可以恢复正常
            System.out.println("断开连接");
        }else if (state == Event.KeeperState.Expired){
            // 没有在会话超时时间内重新连接,而是当会话超时被移除的时候重连会走进这里
            System.out.println("连接过期");
        }else if (state == Event.KeeperState.AuthFailed){
            // 在操作的时候权限不够会出现
            System.out.println("授权失败");
        }
        countDownLatch.countDown();
    }
    private static final String IP = "127.0.0.1:2181"
;
    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws Exception {
        // 5000为会话超时时间
        ZooKeeper zooKeeper = new ZooKeeper(IP, 5000, new ZkConnectionWatcher());
        countDownLatch.await();
        // 模拟授权失败
        zooKeeper.addAuthInfo("digest1","linxianqin:123451".getBytes());
        byte[] data = zooKeeper.getData("/hadoop", false, null);
        System.out.println(new String(data));
        TimeUnit.SECONDS.sleep(50);
    }
}

watcher检查节点

  • exists(String path, boolean b)

  • exists(String path, Watcher w)

  • NodeCreated节点创建

  • NodeDeleted节点删除

  • NodeDataChanged节点内容

案例

public class EventTypeTest {
    private static final String IP = "127.0.0.1:2181";
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    private static ZooKeeper zooKeeper;

    // 采用zookeeper连接创建时的监听器
    public static void exists1() throws Exception{
        zooKeeper.exists("/watcher1",true);
    }
    // 自定义监听器
    public static void exists2() throws Exception{
        zooKeeper.exists("/watcher1",(WatchedEvent w) -> {
            System.out.println("自定义" + w.getType());
        });
    }
    // 演示使用多次的监听器
    public static void exists3() throws Exception{
        zooKeeper.exists("/watcher1", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try {
                    System.out.println("自定义的" + watchedEvent.getType());
                } finally {
                    try {
                        zooKeeper.exists("/watcher1",this);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
    // 演示一节点注册多个监听器
    public static void exists4() throws Exception{
        zooKeeper.exists("/watcher1",(WatchedEvent w) -> {
            System.out.println("自定义1" + w.getType());
        });
        zooKeeper.exists("/watcher1", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try {
                    System.out.println("自定义2" + watchedEvent.getType());
                } finally {
                    try {
                        zooKeeper.exists("/watcher1",this);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
    // 测试
    public static void main(String[] args) throws Exception {
        zooKeeper = new ZooKeeper(IP, 5000, new ZKWatcher());
        countDownLatch.await();
        exists4();
        TimeUnit.SECONDS.sleep(50);
    }
    static class ZKWatcher implements Watcher{
        @Override
        public void process(WatchedEvent watchedEvent) {
            countDownLatch.countDown();
            System.out.println("zk的监听器" + watchedEvent.getType());
        }
    }
}
0

评论区