尚硅谷zookeeper入门笔记

1 Zookeeper基础概念

1.1 概念

  • Zookeeper 是一个开源的分布式的,为分布式框架提供协调服务的 Apache 项目。
  • Zookeeper工作机制:Zookeeper从设计模式角度来理解:是一个基 于观察者模式设计的分布式服务管理框架,它负 责 存储和管理大家都关心的数据,然 后接受观察者的 注 册,一旦这些数据的状态发生变化,Zookeeper 就 将负责通知已经在Zookeeper上注册的那些观察 者做出相应的反应。
    通俗来说,就是zookeeper用于管理服务器节点提供的服务,并且客户端能够在zookeeper注册监听这些服务器的状态,一旦发生服务器上下线事件,客户端可以收取到相应的信息,zookeeper相当于一个有通知机制的文件系统。

1.2 Zookeeper特点

  • 在这里插入图片描述

  • Zookeeper的特点:

    1)Zookeeper:一个领导者(Leader),多个跟随者(Follower)组成的集群。 (不存在歧义的操作可以跟follower进行通信,否则需跟leader进行通信)
    2)集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。所 以Zookeeper适合安装奇数台服务器。
    3)全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的。
    4)更新请求顺序执行,来自同一个Client的更新请求按其发送顺序依次执行。
    5)数据更新原子性,一次数据更新要么成功,要么失败。
    6)实时性,在一定时间范围内,Client能读到最新数据。

  • ZooKeeper 数据模型的结构与 Unix 文件系统很类似,整体上可以看作是一棵树,每个 节点称做一个 ZNode。每一个 ZNode 默认能够存储 1MB 的数据,每个 ZNode 都可以通过 其路径唯一标识。

1.3 Zookeeper提供的服务

  • ZooKeeper提供的服务包括:提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下 线、软负载均衡等。
  • 统一命名服务:在分布式环境下,经常需要对应用/服 务进行统一命名,便于识别。
  • 统一配置管理:
    1)分布式环境下,配置文件同步非常常见。 (1)一般要求一个集群中,所有节点的配置信息是 一致的,比如 Kafka 集群。(2)对配置文件修改后,希望能够快速同步到各个 节点上。
    2)配置管理可交由ZooKeeper实现。 (1)可将配置信息写入ZooKeeper上的一个Znode。(2)各个客户端服务器监听这个Znode。 (3)一 旦Znode中的数据被修改,ZooKeeper将通知 各个客户端服务器。
  • 统一集群管理:
    1)分布式环境中,实时掌握每个节点的状态是必要的。 (1)可根据节点实时状态做出一些调整。
    2)ZooKeeper可以实现实时监控节点状态变化 (1)可将节点信息写入ZooKeeper上的一个ZNode。 (2)监听这个ZNode可获取它的实时状态变化。
  • 服务器节点动态上下线:客户端能够实时洞察到服务器上下线的变化
  • 软负载均衡:在Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xbtEe0YP-1658636637753)(D:\documents\notes\md\images\zookeeper\image-20220720201912240.png)]

2 Zookeeper安装

2.1 zookeeper本地安装

  • 访问官网https://zookeeper.apache.org/下载相应的版本且后缀名为bin.tar.gz的安装包

  • 上传到linux指定路径中进行解压并修改解压得到的文件夹名称为zookeeper-3.5.7

    mv apache-zookeeper-3.5.7-bin/ zookeeper-3.5.7
    
  • 将/opt/module/zookeeper-3.5.7/conf 这个路径下的 zoo_sample.cfg 修改为 zoo.cfg

  • 在/opt/module/zookeeper-3.5.7下创建zookeeper目录存储目录zkData

  • 打开 zoo.cfg 文件,指定zookeeper数据存储路径,修改 dataDir 路径为/opt/module/zookeeper-3.5.7/zkData

  • 相关启动关闭命令

    # 开启zookeeper服务
    bin/zkServer.sh start
    # 查看zookeeper服务状态
    bin/zkServer.sh status
    # 启动客户端
    bin/zkCli.sh
    # 退出客户端
    quit
    # 停止zookeeper服务
    bin/zkServer.sh stop
    

2.2 zookeeper配置

  • zookeeper配置文件zoo.cfg中的参数
    1)tickTime = 2000:通信心跳时间,Zookeeper服务器与客户端心跳时间,单位毫秒。配置服务器与客户端或服务器与服务器之间的通信心跳时间。
    2)initLimit = 10:LF初始通信时限。表示Leader和Follower初始连接时能容忍的时间,10代表10个tickTime,超过该时间则认为通信失败。
    3)syncLimit = 5:LF同步通信时限。Leader和Follower之间通信时间如果超过syncLimit * tickTime,Leader认为Follwer死 掉,从服务器列表中删除Follwer。
    4)dataDir:保存Zookeeper中的数据。注意:默认的tmp目录,容易被Linux系统定期删除,所以一般不用默认的tmp目录。
    5)clientPort = 2181:客户端连接端口,通常不做修改。

2.3 zookeeper集群安装

  • 做好集群规划,即需要在集群哪些节点上部署zookeeper。目前只有三个节点hadoop102、103、104,三个节点都部署上zookeeper,因为zookeeper至少需要三台服务器

  • 在hadoop102上解压zookeeper安装包,同1.4进行配置

  • 在/opt/module/zookeeper-3.5.7/zkData 目录下创建一个 myid 的文件,在文件中添加与服务器相对应的编号,hadoop102用编号2。分发后在hadoop103中的myid文件中编号改为3,hadoop104中myid文件中编号改为4,以此类推。

  • 在conf/下的配置文件zoo.cfg中添加以下配置:并分发到各个zookeeper节点上

    #######################cluster##########################
    server.2=hadoop102:2888:3888
    server.3=hadoop103:2888:3888
    server.4=hadoop104:2888:3888
    

    server.A=B:C:D
    A 是一个数字,表示这个是第几号服务器; 集群模式下配置一个文件 myid,这个文件在 dataDir 目录下,这个文件里面有一个数据 就是 A 的值,Zookeeper 启动时读取此文件,拿到里面的数据与 zoo.cfg 里面的配置信息比 较从而判断到底是哪个 server。
    B 是这个服务器的地址;
    C 是这个服务器 Follower 与集群中的 Leader 服务器交换信息的端口;
    D 是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。

  • 在每个服务器中都启动zookeeper,会通过选举机制来产生follower跟leader

3 Zookeeper操作

3.1 Zookeeper选举机制(面试重点)

  • zookeeper集群第一次启动的选举机制:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iIvs9rD6-1658636637754)(D:\documents\notes\md\images\zookeeper\image-20220722122009062.png)]

  • zookeeper集群非第一次启动的选举机制:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RZ2bizt5-1658636637754)(D:\documents\notes\md\images\zookeeper\image-20220722122813808.png)]

3.2 Zookeeper集群启动停止脚本

  • #!/bin/bash
    case $1 in
    "start"){
        for i in hadoop102 hadoop103 hadoop104
        do
     		echo ---------- zookeeper $i 启动 ------------
    		ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh 
    start"
    	done
    };;
    "stop"){
    	for i in hadoop102 hadoop103 hadoop104
    	do
    	echo ---------- zookeeper $i 停止 ------------ 
    	ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh 
    stop"
    	done
    };;
    "status"){
    	for i in hadoop102 hadoop103 hadoop104
    	do
    		echo ---------- zookeeper $i 状态 ------------ 
    		ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh 
    status"
    	done
    };;
    esac
    

3.3 客户端命令行操作

  • 指定服务器开启客户端

    bin/zkCli.sh -server 主机名称:2181
    
  • # 查看当前znode中所包含的内容
    ls /
    # 查看当前节点详细数据
     ls -s /
    [zookeeper]cZxid = 0x0
    ctime = Thu Jan 01 08:00:00 CST 1970
    mZxid = 0x0
    mtime = Thu Jan 01 08:00:00 CST 1970
    pZxid = 0x0
    cversion = -1
    dataVersion = 0
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 0
    numChildren = 1
    (1)czxid:创建节点的事务 zxid
    每次修改 ZooKeeper 状态都会产生一个 ZooKeeper 事务 ID。事务 ID 是 ZooKeeper 中所有修改总的次序。每次修改都有唯一的 zxid,如果 zxid1 小于 zxid2,那么 zxid1 在 zxid2 之前发生。
    (2)ctime:znode 被创建的毫秒数(从 1970 年开始)
    (3)mzxid:znode 最后更新的事务 zxid
    (4)mtime:znode 最后修改的毫秒数(从 1970 年开始)
    (5)pZxid:znode 最后更新的子节点 zxid
    (6)cversion:znode 子节点变化号,znode 子节点修改次数
    (7)dataversion:znode 数据变化号,znode数据修改次数
    (8)aclVersion:znode 访问控制列表的变化号
    (9)ephemeralOwner:如果是临时节点,这个是 znode 拥有者的 session id。如果不是临时节点则是 0。
    (10)dataLength:znode 的数据长度
    (11)numChildren:znode 子节点数量
    

3.4 Zookeeper节点类型

  • zookeeper包含以下4种类型节点:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fYNE1mya-1658636637755)(D:\documents\notes\md\images\zookeeper\image-20220722161710572.png)]

  • 创建永久不带序号节点(持久化目录节点)操作

    # 创建sanguo节点,描述信息为diaochan
    create /sanguo "diaochan"
    # 在sanguo节点下创建shuguo节点,描述信息为liubei
    create /sanguo/shuguo "liubei"
    
  • 获取节点的具体描述信息

    # 获取/sanguo节点的具体描述信息
    get -s /sanguo
    diaochan # 描述信息
    cZxid = 0x20000000a # 创建节点时的事务id
    ctime = Fri Jul 22 16:18:25 CST 2022 # 创建时间
    mZxid = 0x20000000a # 最后更新的事务id
    mtime = Fri Jul 22 16:18:25 CST 2022 # 最后修改时间
    pZxid = 0x20000000b # 子节点的事务id
    cversion = 1 # 子节点变化号
    dataVersion = 0 # 数据变化号
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 8 # 数据长度
    numChildren = 1 # 子节点个数
    
  • 创建永久带序号节点(持久化顺序编号目录节点)

    # 在/sanguo/weiguo节点下创建zhangliao永久带序号节点,创建后会自动生成序号0000000000,如果执行操作第二次的话,会在节点名后面生成序号0000000001
    create -s /sanguo/weiguo/zhangliao "zhangliao"
    Created /sanguo/weiguo/zhangliao0000000000
    
  • 创建临时不带序号节点,退出客户端后会自动删除节点

    # 在sanguo节点下创建临时节点wuguo
    create -e /sanguo/wuguo "zhouyu"
    
  • 创建临时带序号节点,退出客户端后会自动删除节点

    # 在sanguo节点下创建临时带序号节点wuguo,会自动生成序号
    create -e -s /sanguo/wuguo "zhouyu"
    Created /sanguo/wuguo0000000003
    
  • 修改节点数据值

    # 更改weiguo节点的数据值
    set /sanguo/weiguo "simayi"
    

3.5 监听器原理

  • 客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、节点删除、子目 录节点增加删除)时,ZooKeeper 会通知客户端。监听机制保证 ZooKeeper 保存的任何的数 据的任何改变都能快速的响应到监听了该节点的应用程序。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-byevtP8c-1658636637755)(D:\documents\notes\md\images\zookeeper\image-20220722184133527.png)]

  • 监听节点数据变化:
    在hadoop104主机上注册监听/sanguo 节点数据变化

    get -w /sanguo
    

    在hadoop103修改sanguo节点的数据,hadoop104会收到监听,但是如果多次修改,只会收到一次监听,因为注册 一次,只能监听一次。想再次监听,需要再次注册。

    WATCHER::
    
    WatchedEvent state:SyncConnected type:NodeDataChanged path:/sanguo
    
  • 节点的子节点变化监听(路径变化):
    在 hadoop104 主机上注册监听/sanguo 节点的子节点变化

     ls -w /sanguo
    

    在 hadoop103 主机/sanguo 节点上创建子节点,hadoop104会收到子节点变化的监听,但是如果多次创建,也是只会收到一次监听,想再次监听,需要再次注册。

    WATCHER::
    
    WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/sanguo
    
  • 删除节点

    # 删除节点
     delete /sanguo/jin
    # 递归删除节点
    deleteall /sanguo/shuguo
    # 查看节点状态
    stat /sanguo
    

3.6 客户端API操作

  • 环境搭建
    1)新建maven工程zookeeper
    2)pom文件添加依赖

    <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>RELEASE</version>
            </dependency>
            <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
            </dependency>
            <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
                <version>3.5.7</version>
            </dependency>
    </dependencies>
    

    3)拷贝log4j.properties文件到src/main/resources

    log4j.rootLogger=INFO, stdout 
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender 
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
    log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n 
    log4j.appender.logfile=org.apache.log4j.FileAppender 
    log4j.appender.logfile.File=target/spring.log 
    log4j.appender.logfile.layout=org.apache.log4j.PatternLayout 
    log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
    

    4)创建包com.F.zk

    5)创建类zkClient

  • 创建zookeeper客户端,zookeeper对象中的process方法在zookeeper对象调用各种方法请求服务器时会调用

    package com.F.zk;
    
    import org.apache.zookeeper.WatchedEvent;
    import org.apache.zookeeper.Watcher;
    import org.apache.zookeeper.ZooKeeper;
    import org.junit.Test;
    
    import java.io.IOException;
    
    public class zkClient {
    
        // 注意:逗号左右不能有空格
        private String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
        private int sessionTimeout = 2000;
    	private ZooKeeper zkClient;
        
        @Test
        public void init() throws IOException {
            	zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
    
                }
            });
        }
    
    }
    
    
  • 创建子节点,调用zookeeper客户端对象的create方法,需要传入4个参数,分别是节点路径,节点数据,节点权限,节点类型

    package com.F.zk;
    
    import org.apache.zookeeper.*;
    import org.junit.Before;
    import org.junit.Test;
    
    import java.io.IOException;
    
    public class zkClient {
    
        // 注意:逗号左右不能有空格
        private String connectString = "192.168.10.102:2181,192.168.10.103:2181,192.168.10.104:2181";
        private int sessionTimeout = 400000;
        private ZooKeeper zkClient;
    
        @Before
        public void init() throws IOException {
            zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
    
                }
            });
        }
    
        @Test
        public void create() throws InterruptedException, KeeperException {
            String nodeCreated = zkClient.create("/F", "abc".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    }
    
    
  • 获取子节点并监听节点变化,调用zookeeper客户端对象的getChildren方法,需要传入监听节点路径和监听器(也可以传入参数true,使用客户端对象自带的监听器对象),这里如果直接运行我们定义的getChildren函数,里面调用zkClient.getChildren,由于设置了getChildren第二个参数为true,所以会走zkClient对象的process方法,但是只会对服务器注册监听一次,这样的话当增加或删除节点时就不会打印输出节点变化,因此还需要在初始化方法中,创建zkClient对象时重写process方法重新注册监听,这样第一次运行getChildren方法后,走process方法的时候就会重新注册监听,就能够一直监听下去了。

    package com.F.zk;
    
    import org.apache.zookeeper.*;
    import org.junit.Before;
    import org.junit.Test;
    
    import java.io.IOException;
    import java.util.List;
    
    public class zkClient {
    
        // 注意:逗号左右不能有空格
        private String connectString = "192.168.10.102:2181,192.168.10.103:2181,192.168.10.104:2181";
        private int sessionTimeout = 400000;
        private ZooKeeper zkClient;
    
        @Before
        public void init() throws IOException {
            zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    System.out.println("----------------------");
                    List<String> children = null;
                    try {
                        children = zkClient.getChildren("/", true);
                    } catch (KeeperException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    for (String child : children) {
                        System.out.println(child);
                    }
                }
            });
        }
    
        @Test
        public void create() throws InterruptedException, KeeperException {
            String nodeCreated = zkClient.create("/F", "abc".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    
        @Test
        public void getChildren() throws InterruptedException, KeeperException {
            List<String> children = zkClient.getChildren("/", true);
            for (String child : children) {
                System.out.println(child);
            }
            // 延时
            Thread.sleep(Long.MAX_VALUE);
        }
    
    
    }
    
  • 判断znode节点是否存在

package com.F.zk;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.List;

public class zkClient {

    // 注意:逗号左右不能有空格
    private String connectString = "192.168.10.102:2181,192.168.10.103:2181,192.168.10.104:2181";
    private int sessionTimeout = 4000000;
    private ZooKeeper zkClient;

    @Before
    public void init() throws IOException {
        zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
//                System.out.println("----------------------");
//                List<String> children = null;
//                try {
//                    children = zkClient.getChildren("/", true);
//                } catch (KeeperException e) {
//                    e.printStackTrace();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                for (String child : children) {
//                    System.out.println(child);
//                }
            }
        });
    }

    @Test
    public void create() throws InterruptedException, KeeperException {
        String nodeCreated = zkClient.create("/F", "abc".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    @Test
    public void getChildren() throws InterruptedException, KeeperException {
        List<String> children = zkClient.getChildren("/", true);
        for (String child : children) {
            System.out.println(child);
        }
        // 延时
        Thread.sleep(Long.MAX_VALUE);
    }

    @Test
    public void exist() throws InterruptedException, KeeperException {
        Stat stat = zkClient.exists("/F", false);
        System.out.println(stat == null ? "no exist" : "exist");
    }
}

3.7 客户端向服务端写数据原理

  • 客户端的写入请求直接发送给Leader节点的流程[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X7kfRCbN-1658636637755)(D:\documents\notes\md\images\zookeeper\image-20220723112235340.png)]

    1. clent向leader节点发送写入请求
    2. leader节点进行写数据并同时将写请求通知给follower节点
    3. follower节点向leader节点发送写成功确认信息
    4. 当有半数以上节点写成功后,leader节点向客户端发送写成功确认信息
    5. 剩余follower节点继续执行写操作,并向leader节点发送信息
  • 客户端的写入请求直接发送给follower节点的流程[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D7WDexX0-1658636637755)(D:\documents\notes\md\images\zookeeper\image-20220723114258258.png)]

    1. client向follower节点发送写入请求
    2. follower节点转发写入请求给leader节点
    3. leader节点进行写数据并同时把写请求转发给follower
    4. follower节点写完后会给leader节点一个应答
    5. 当有半数以上节点写成功后,leader节点向follower节点发送写成功应答
    6. follower节点向client发送写成功应答
    7. 剩余follower节点继续执行写操作,并向leader节点发送信息

4 服务器动态上下线监听案例

  • 需求:某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知 到主节点服务器的上下线。

  • 服务器上线:即服务器可以对外提供服务,在zookeeper集群上创建对应节点,节点信息为服务器主机名称以及当前连接的客户端数等

  • 流程:
    1)服务端启动时向zookeeper注册信息(注册临时节点)(服务器上线)
    2)客户端获取当前在线服务器列表并注册监听
    3)服务器节点与zookeeper集群失去联系(服务器节点下线,临时节点删除)
    4)客户端收到服务器节点下线通知,执行相应操作并再次注册监听
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ubm8Q67M-1658636637756)(D:\documents\notes\md\images\zookeeper\image-20220723115847246.png)]

  • 服务器跟客户端对zookeeper集群来说都是客户端,上面的客户端(服务器)是到zookeeper集群上创建节点,下面的客户端(用户)是对zookeeper集群的一个监听。

  • 具体实现:
    (1)先在集群上创建/servers 节点
    (2)在 Idea 中创建包名:com.atguigu.zkcase1
    (3)服务器端向 Zookeeper 注册代码

    package com.F.case1;
    
    import org.apache.zookeeper.*;
    
    import java.io.IOException;
    
    public class DistributeServer {
    
        private String connectString = "192.168.10.102:2181,192.168.10.103:2181,192.168.10.104:2181";
        private int sessionTimeout = 4000000;
        private ZooKeeper zk;
    
        public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
    
            DistributeServer server = new DistributeServer();
            // 1 获取zk连接
            server.getConnect();
            // 2 注册服务器到zk集群(创建节点)
            server.regist(args[0]);
            // 3 启动业务逻辑(等待触发)
            server.buisness();
        }
    
        private void buisness() throws InterruptedException {
            Thread.sleep(Long.MAX_VALUE);
        }
    
        private void regist(String hostname) throws InterruptedException, KeeperException {
            // 创建带序号临时节点
            String create = zk.create("/servers/" + hostname, hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(hostname + " is online");
        }
    
        private void getConnect() throws IOException {
            zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
    
                }
            });
        }
    }
    
    

    (4)客户端代码

    package com.F.case1;
    
    import org.apache.zookeeper.KeeperException;
    import org.apache.zookeeper.WatchedEvent;
    import org.apache.zookeeper.Watcher;
    import org.apache.zookeeper.ZooKeeper;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    public class DistributeClient {
    
        private String connectString = "192.168.10.102:2181,192.168.10.103:2181,192.168.10.104:2181";;
        private int sessionTimeout = 400000;
        private ZooKeeper zk;
    
        public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
            DistributeClient client = new DistributeClient();
            // 1 获取zk连接
            client.getConnect();
            // 2 监听/servers下面子节点的增加和删除
            client.getServerList();
            // 3 业务逻辑(等待触发)
            client.business();
        }
    
        private void business() throws InterruptedException {
            Thread.sleep(Long.MAX_VALUE);
        }
    
        private void getServerList() throws InterruptedException, KeeperException {
            List<String> children = zk.getChildren("/servers", true);
            ArrayList<String> servers = new ArrayList<>();
            for (String child : children) {
                byte[] data = zk.getData("/servers/" + child, false, null);
                servers.add(new String(data));
            }
            System.out.println(servers);
        }
    
        private void getConnect() throws IOException {
            zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    try {
                        // 重新注册监听
                        getServerList();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (KeeperException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    
    

    (5)测试,启动客户端监听,再测试服务端

5 Zookeeper分布式锁案例

  • 什么叫做分布式锁呢? 比如说"进程 1"在使用该资源的时候,会先去获得锁,"进程 1"获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,"进程 1"用完该资源以后就将锁释放掉,让其 他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的 访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。

  • zookeeper分布式锁案例流程: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E2Ovmvh3-1658636637756)(D:\documents\notes\md\images\zookeeper\image-20220723135615079.png)]

  • 代码实现
    创建DistributedLock类,定义了连接、加锁、解锁操作

    package com.F.case2;
    
    import org.apache.zookeeper.*;
    import org.apache.zookeeper.data.Stat;
    
    import java.io.IOException;
    import java.util.Collections;
    import java.util.List;
    import java.util.concurrent.CountDownLatch;
    
    public class DistributedLock {
        private final String connectString = "192.168.10.102:2181,192.168.10.103:2181,192.168.10.104:2181";
        private final int sessionTimeout = 400000;
        private final ZooKeeper zk;
    
        private CountDownLatch connectLatch = new CountDownLatch(1);
        private CountDownLatch waitLatch = new CountDownLatch(1);
    
        private String waitPath;
        private String currentMode;
    
        public DistributedLock() throws IOException, InterruptedException, KeeperException {
            // 获取连接
            zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    // connectLatch 如果连接上zk可以释放
                    if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
                        connectLatch.countDown();
                    }
                    // waitLatch需要释放
                    if (watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)) {
                        waitLatch.countDown();
                    }
                }
            });
    
            // 等待zk正常连接后,往下走程序
            connectLatch.await();
            // 判断集群根节点locks是否存在
            Stat stat = zk.exists("/locks", false);
            if (stat == null) {
                // 创建/locks节点
                zk.create("/locks", "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        }
    
        // 对zk加锁
        public void zkLock() {
            // 创建对应的临时带序号节点
            try {
                currentMode = zk.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
                // 判断创建的节点是否序号最小的节点,如果是,则获取到锁,如果不是,则监听序号前一个节点
                List<String> children = zk.getChildren("/locks", false);
                // 如果children只有一个值,则直接获取锁,如果有多个阶段,需要判断谁最小
                if (children.size() == 1)
                    return;
                else {
                    // 对children排序
                    Collections.sort(children);
                    // 获取节点名称, 去掉/locks/,只取后面的节点名称
                    String thisNode = currentMode.substring("/locks/".length());
                    // 通过当前节点名称获取该节点再children中的位置
                    int index = children.indexOf(thisNode);
                    if (index == -1) {
                        System.out.println("数据异常");
                    } else if (index == 0){
                        // 是第一个节点,可以获取锁
                        return;
                    } else {
                        // 需要监听前一个节点变化
                        waitPath = "/locks/" + children.get(index - 1);
                        zk.getData(waitPath, true, null);
    
                        // 等待监听
                        waitLatch.await();
    
                        return;
                    }
                }
    
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
    
        }
      
        // 解锁
        public void unZkLock() {
            // 删除节点
            try {
                zk.delete(currentMode, -1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (KeeperException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    测试分布式锁,lock1和lock2模拟客户端对zookeeper集群的两个请求,分别进入到zkLock函数中会创建各自对应的临时带序号节点,序号为小的节点可以直接通过zkLock方法,调用线程等待方法模拟获取资源操作,然后再调用unZkLock方法删除临时节点;序号大的节点会走到zkLock方法中最后一部分,监听另一个节点的情况,通过getData方法来访问到zookeeper对象的process方法,从而释放watchLatch,然后就能通过zkLock方法,调用线程等待方法模拟获取资源操作,然后调用unZkLock方法删除临时节点(释放watchLatch相当于第一个请求已经释放资源解除锁了)。

    package com.F.case2;
    
    import org.apache.zookeeper.KeeperException;
    
    import java.io.IOException;
    
    public class DistributedLockTest {
        
        public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
            final DistributedLock lock1 = new DistributedLock();
            final DistributedLock lock2 = new DistributedLock();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    lock1.zkLock();
                    System.out.println("线程1启动,获取到锁");
                    try {
                        Thread.sleep(5 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock1.unZkLock();
                    System.out.println("线程1释放锁");
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    lock2.zkLock();
                    System.out.println("线程2启动,获取到锁");
                    try {
                        Thread.sleep(5 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock2.unZkLock();
                    System.out.println("线程2释放锁");
                }
            }).start();
        }
    }
    
    
  • Curator框架实现分布式锁案例:
    1)原生的 Java API 开发存在的问题
    (1)会话连接是异步的,需要自己去处理。比如使用 CountDownLatch
    (2)Watch 需要重复注册,不然就不能生效
    (3)开发的复杂性还是比较高的
    (4)不支持多节点删除和创建。需要自己去递归
    2)Curator 是一个专门解决分布式锁的框架,解决了原生 JavaAPI 开发分布式遇到的问题。 详情请查看官方文档:https://curator.apache.org/index.html

  • Curator案例实操
    (1)添加依赖

    <dependency>
         <groupId>org.apache.curator</groupId>
         <artifactId>curator-framework</artifactId>
         <version>4.3.0</version>
    </dependency>
    <dependency>
         <groupId>org.apache.curator</groupId>
         <artifactId>curator-recipes</artifactId>
         <version>4.3.0</version>
    </dependency>
    <dependency>
         <groupId>org.apache.curator</groupId>
         <artifactId>curator-client</artifactId>
         <version>4.3.0</version>
    </dependency>
    

    (2)代码实现,通过框架来连接到zookeeper集群,并且实现分布式锁

    package com.F.case3;
    
    
    import org.apache.curator.framework.CuratorFramework;
    import org.apache.curator.framework.CuratorFrameworkFactory;
    import org.apache.curator.framework.recipes.locks.InterProcessMutex;
    import org.apache.curator.retry.ExponentialBackoffRetry;
    
    public class CuraotrLockTest {
        public static void main(String[] args) {
            // 创建分布式锁1
            InterProcessMutex lock1 = new InterProcessMutex(getCuratorFramework(), "/locks");
            // 创建分布式锁2
            InterProcessMutex lock2 = new InterProcessMutex(getCuratorFramework(), "/locks");
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        lock1.acquire();
                        System.out.println("线程1 获取到锁");
                        lock1.acquire();
                        System.out.println("线程1 再次获取到锁");
                        Thread.sleep(5 * 1000);
                        lock1.release();
                        System.out.println("线程1 释放锁");
                        lock1.release();
                        System.out.println("线程1 再次释放锁");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        lock2.acquire();
                        System.out.println("线程2 获取到锁");
                        lock2.acquire();
                        System.out.println("线程2 再次获取到锁");
                        Thread.sleep(5 * 1000);
                        lock2.release();
                        System.out.println("线程2 释放锁");
                        lock2.release();
                        System.out.println("线程2 再次释放锁");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    
        private static CuratorFramework getCuratorFramework() {
    
            // 设置连接失败重试策略,3秒后重试3次
            ExponentialBackoffRetry policy = new ExponentialBackoffRetry(3000, 3);
    
            CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.10.102:2181,192.168.10.103:2181,192.168.10.104:2181")
                    .connectionTimeoutMs(400000)
                    .sessionTimeoutMs(400000)
                    .retryPolicy(policy).build();
    
            // 启动客户端
            client.start();
            System.out.println("zookeeper启动成功");
    
            return client;
        }
    }
    
    

6 企业面试真题

  • 选举机制:半数机制,超过半数的投票通过,即通过。
    (1)第一次启动选举规则: 投票过半数时,服务器 id 大的胜出
    (2)第二次启动选举规则: ①EPOCH 大的直接胜出 ②EPOCH 相同,事务 id 大的胜出 ③事务 id 相同,服务器 id 大的胜出
  • 生产集群安装多少 zk 合适?
    安装奇数台。
    生产经验: 10 台服务器:3 台 zk; 20 台服务器:5 台 zk; 100 台服务器:11 台 zk; 200 台服务器:11 台 zk 服务器
    台数多:好处,提高可靠性;坏处:提高通信延时
  • 常用命令:ls、get、create、delete
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值