Zookeeper的介绍及使用

一、Zookeeper概述

zookeeper主要是文件系统和通知机制

  • 文件系统主要是用来存储数据
  • 通知机制主要是服务器或者客户端进行通知,并且监督

基于观察者模式设计的分布式服务管理框架,开源的分布式框架

1.1.特点

  • 一个leader,多个follower的集群
  • 集群只要有半数以上包括半数就可正常服务,一般安装奇数台服务器
  • 全局数据一致,每个服务器都保存同样的数据,实时更新
  • 更新的请求顺序保持顺序(来自同一个服务器)
  • 数据更新的原子性,一次数据更新要么成功要么失败
  • 实时性,在一定时间范围内,Client能读到最新数据。数据实时更新性很快。

1.2.主要的集群步骤为

  1. 服务端启动时去注册信息(创建都是临时节点)
  2. 获取到当前在线服务器列表,并且注册监听
  3. 服务器节点下线
  4. 服务器节点上下线事件通知
  5. process(){重新再去获取服务器列表,并注册监听}

1.3.数据结构

与 Unix 文件系统很类似,可看成树形结构,每个节点称做一个 ZNode。每一个 ZNode 默认能够存储 1MB 的数据。也就是只能存储小数据。每个ZNode都可以通过其路径唯一标识。

Zookeeper的介绍及使用_第1张图片

1.4.应用场景

  • 统一命名服务(域名服务)

    在分布式环境下,经常需要对应用/服务进行统一命名,便于识别。

    例如:IP不容易记住,而域名容易记住。

Zookeeper的介绍及使用_第2张图片

  • 统一配置管理(一个集群中的所有配置都一致,且也要实时更新同步)
    将配置信息写入ZooKeeper上的一个Znode,各个客户端服务器监听这个Znode。一旦Znode中的数据被修改,ZooKeeper将通知各个客户端服务器。

    分布式环境下,配置文件同步非常常见。

    • 一般要求一个集群,所有节点的配置信息是一致的,比如Kafka集群。
    • 对配置文件修改后,希望能够快速同步到各个节点上。

Zookeeper的介绍及使用_第3张图片

  • 统一集群管理(在分布式环境中,实时掌握每个节点的状态)
    Zookeeper可以实现实时监控节点状态变化。

    将节点信息写入ZooKeeper上的一个ZNode。监听ZNode获取实时状态变化

Zookeeper的介绍及使用_第4张图片

  • 服务器节点动态上下线

Zookeeper的介绍及使用_第5张图片

  • 软负载均衡(根据每个节点的访问数,让访问数最少的服务器处理最新的数据需求)

Zookeeper的介绍及使用_第6张图片

二、Zookeeper的下载安装

2.1.官网地址

https://zookeeper.apache.org/

2.2.下载步骤

访问官网,点击下载

Zookeeper的介绍及使用_第7张图片

下拉选择版本

Zookeeper的介绍及使用_第8张图片

选择版本之后点击下载,以linux为例,选择.tar.gz文件

Zookeeper的介绍及使用_第9张图片

Zookeeper的介绍及使用_第10张图片

2.3.安装

2.3.1.安装前的准备

1.安装JDK

2.将zookeeper-3.4.9.tar.gz安装包拷贝到Linux系统下

2.3.2.安装

在安装包路径下将zookeeper-3.4.9.tar.gz解压

之后进入zookeeper-3.4.9文件夹中,创建一个存储数据的文件夹并进入文件夹使用pwd获取文件路径

mkdir zkData

image-20220629184119345

image-20220629184102366

修改zookeeper-3.4.9目录下中的conf下的zoo_sample.cfg文件名,改为zoo.cfg

Zookeeper的介绍及使用_第11张图片

将zoo.cfg中的dataDir路径修改为zkData的路径

Zookeeper的介绍及使用_第12张图片

之后启动服务端在启动客户端
具体服务端的启动通过bin目录下的文件
通过zkServer.sh start或者是./zkServer.sh start
image-20220630093834708

查看zookeeper的状态

bin/zkServer.sh status

Zookeeper的介绍及使用_第13张图片

启动客户端

bin/zkCli.sh

Zookeeper的介绍及使用_第14张图片

退出客户端

quit

image-20220630094124475

停止zookeeper

bin/zkServer.sh stop

image-20220630094143881

三、Zookeeper配置文件

zoo.cfg中参数的含义:

1.tickTime = 2000 :通信心跳时间,Zookeeper服务器与客户端心跳时间,单位毫秒

Zookeeper的介绍及使用_第15张图片

2.initLimit = 10 :LF初始通信时限

Leader和Follow初始连接时能容忍的最多心跳数(tickTime的数量) 即时间最多不能超过tickTime * initLimit的时间建立连接。

Zookeeper的介绍及使用_第16张图片

3.syncLimit = 5:LF同步通信时间

Leader和Follow之间通信时间如果超过syncLimit * tickTime,Leader认为Follow死掉,从服务器列表中删除Follow

4.dataDir:保存Zookeeper中的数据

注意:默认的tmp目录,容易被系统定期删除,所以一般不用默认的tmp目录。

5.clientPort = 2181,客户端连接端口,通常不做修改。

四、Zookeeper集群操作

4.1.集群安装

克隆两台虚拟机,分别配置不同的ip地址

之后在zookeeper/zkData目录下创建myid文件。

vim myid

在文件中添加与server对应的编号(注意:上下不要有空行,左右不要有空格)(每一台虚拟机的server对应编号都不一样)

2

注意:添加myid文件,一定要在Linux里面创建,在notepad++里面很可能乱码

②修改zoo.cfg配置文件

增加如下配置,关闭每台虚拟机的防火墙(systemctl stop firewalld)

################################cluster##############################
server.1=192.168.200.131:2888:3888
server.2=192.168.200.132:2888:3888
server.3=192.168.200.133:2888:3888

参数解读

当前主要配置编号的参数是server.A=B:C:D

A标识第几台服务器
B标识服务器地址
C标识服务器 Follower 与集群中的 Leader 服务器交换信息的端口
D主要是选举,如果Leader 服务器挂了。这个端口就是用来执行选举时服务器相互通信的端口,通过这个端口进行重新选举leader
配置文件以及服务器编号设置好后即可启动

③启动每台虚拟机中的zookeeper

zkServer.sh start

查看每台zookeeper的状态

image-20220630110215238

image-20220630110138282

image-20220630110154355

4.2.选举机制

区分好第一次启动与非第一次启动的步骤

Zookeeper第一次启动的选举机制

epoch在没有leader的时候,主要是逻辑时钟(与数字电路中的逻辑时钟相同)

Zookeeper的介绍及使用_第17张图片

Zookeeper非第一次启动的选举机制

Zookeeper的介绍及使用_第18张图片

Zookeeper的介绍及使用_第19张图片

4.3.客户端命令

4.3.1.常用命令

Zookeeper的介绍及使用_第20张图片

通过ls /
查看当前znode包含的信息

image-20220630114205647

查看当前数据节点详细信息

get /zookeeper

Zookeeper的介绍及使用_第21张图片

Zookeeper的介绍及使用_第22张图片

4.3.2.节点类型

节点类型分为(两两进行组合)

  • 持久/短暂
  • 有序号/无序号

Zookeeper的介绍及使用_第23张图片

创建节点不带序号的
通过create 进行创建

Zookeeper的介绍及使用_第24张图片

获取创建节点中命令的值

Zookeeper的介绍及使用_第25张图片

如果创建节点带序号的通过加上-s参数
即便创建两个一样的,-s 自动会加上序号辨别两者不同

重启zkCli.sh之后,发现该结点仍然存在。

如果创建临时节点 加上参数 -e

临时节点不带序号-e

image-20220630153210764

临时节点带序号-e -s
如果退出客户端,这些短暂节点将会被清除

image-20220630153322137

断开客户端之后重启发现临时节点都被清除。

如果修改节点的值,通过set key value即可

Zookeeper的介绍及使用_第26张图片

4.3.3.Zookeeper监听器原理

ZK客户端去ZK服务端中注册,说明要对哪个结点进行监听,注册完成之后,一旦监听的结点发生变化,ZK服务端就会去通知ZK客户端。

Zookeeper的介绍及使用_第27张图片

监听主要监听

  • 节点数据的变化
  • 子节点增减的变化

3.5版本以下

监听数据值的变化

get /sanguo watch

3.5版本以上

监听数据值的变化

get -w /sanguo

在其中一台zookeeper客户端中监听/sanguo节点的变化,在另一台zookeeper客户端中修改/sanguo节点的值。

监听结点

Zookeeper的介绍及使用_第28张图片

另一台中修改结点值

Zookeeper的介绍及使用_第29张图片

监听处显示如下
在这里插入图片描述

此时如果另一台中再修改节点的值,监听处不会再显示监听结果,因为注册一次,就监听一次。

3.5版本以下

监听子节点的变化

ls /sanguo watch

3.5版本以上

监听数据值的变化

ls -w /sanguo

在其中一台zookeeper客户端中监听/sanguo节点的变化,在另一台zookeeper客户端中创建/sanguo的子节点。

监听结点
在这里插入图片描述

另一台zookeeper客户端中创建节点
在这里插入图片描述

监听处显示如下

在这里插入图片描述

4.3.4.节点删除

删除某个结点

delete /sanguo/jin

删除全部节点

3.5版本以前

rmr /sanguo

3.5版本之后

deleteall /sanguo

查看结点状态

stat /sanguo

4.4.客户端API操作

前提:启动zookeeper集群。

4.4.1.IDEA环境搭建

①创建maven工程

②添加pom文件

    <dependencies>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.apache.logging.log4jgroupId>
            <artifactId>log4j-coreartifactId>
            <version>2.8.2version>
        dependency>
        <dependency>
            <groupId>org.apache.zookeepergroupId>
            <artifactId>zookeeperartifactId>
            <version>3.4.9version>
        dependency>
    dependencies>

③拷贝log4j.properties文件到项目根目录

需要在项目的src/main/resources目录下,创建一个文件,命名为"log4j.properties",在文件中填入

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

④在客户端中生成具体代码

初始化并且负责监听节点
主要通过设置连接的服务器,以及超时参数,监听匿名函数new Watcher() {}

public class ZkClient {

    //注意:逗号左右不能有空格
    private String connectString ="192.168.200.131:2181,192.168.200.132,192.168.200.133";
    private int sessionTimeout = 2000;
    @Test
    public void init() throws IOException {
        ZooKeeper zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            public void process(WatchedEvent watchedEvent) {

            }
        });
    }
}

创建一个新的节点
创建的节点必须在初始化节点这些之后,通过注解,在之前中加入before

  • 第一个参数是路径
  • 第二个参数是数据,要求是字节类型,需要用getBytes()
  • 第三个参数是权限,Ids.OPEN_ACL_UNSAFE允许所有人进行访问
  • 第四个参数是创建节点的类型
    @Test
    public void create() throws KeeperException, InterruptedException {
        String nodeCreated = zkClient.create("/zkt", "8811".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

    }

执行之后可以看到zkt结点创建成功

在这里插入图片描述

获取子节点并且监听其变化
获取子节点通过getChildren()
关于获取子节点一共有两种

在这里插入图片描述

获取子节点第一个是路径,第二个是监听函数,如果使用true,则会使用初始化函数中重写的监听函数

    @Test
    public void getChildren() throws KeeperException,InterruptedException{
        List<String> children = zkClient.getChildren("/",true);

        for(String child : children){
            System.out.println(child);
        }
        Thread.sleep(Long.MAX_VALUE);
    }

注册一次生效一次,还需要在进行注册
希望通过延迟函数延迟程序的结束,继续监听Thread.sleep(Long.MAX_VALUE);
放在监听函数中就可以注册一次生效一次,之后在注册

在这里插入图片描述

public class ZkClient {

    //注意:逗号左右不能有空格
    private String connectString ="192.168.200.131:2181,192.168.200.132,192.168.200.133";
    private int sessionTimeout = 2000;
    private ZooKeeper zkClient;

    @Before
    public void init() throws IOException {
        zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            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);
                }

                System.out.println("------------------------------------");
            }
        });
    }

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

    @Test
    public void getChildren() throws KeeperException,InterruptedException{
        List<String> children = zkClient.getChildren("/",true);

        for(String child : children){
            System.out.println(child);
        }

        Thread.sleep(Long.MAX_VALUE);
    }

}

创建zkt2结点之后可以立刻监听到。

Zookeeper的介绍及使用_第30张图片

Zookeeper的介绍及使用_第31张图片

4.5.客户端向服务端写数据流程

发送给leader的时候
通俗解释:客户端给服务器的leader发送写请求,写完数据后给手下发送写请求,手下写完发送给leader,超过半票以上都写了则发回给客户端。之后leader在给其他手下让他们写,写完在发数据给leader

Zookeeper的介绍及使用_第32张图片

发送给follower的时候
通俗解释:客户端给手下发送写的请求,手下给leader发送写的请求,写完后,给手下发送写的请求,手下写完后给leader发送确认,超过半票,leader确认后,发给刻划断,之后leader在发送写请求给其他手下。
Zookeeper的介绍及使用_第33张图片

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

对于Zookeeper集群来说,除了Zookeeper是服务器之外,对于Zookeeper来说,无论是服务器还是客户端,都是客户端。即服务端是在zookeeper中注册服务,客户端在zookeeper中监听结点。

  1. 服务器上线的时候其实就是服务器启动时去注册信息(创建的都是临时节点)
  2. 客户端获取到当前在线的服务器列表后
  3. 服务器节点下线后给集群管理
  4. 集群管理服务器节点的下件时间通知给客户端
  5. 客户端通过获取服务器列表重选选择服务器

Zookeeper的介绍及使用_第34张图片

服务器代码

  • 获取zookeeper集群的连接,通过zookeeper的构造函数ZooKeeper(connectString, sessionTimeout, new Watcher(){})
  • 将其服务注册到zookeeper集群中,具体通过create的函数,通过获取每个服务器名字、其值、权限、节点类型
  • 执行该函数通过延迟函数
public class DistributeServer {
    private String connectionString = "192.168.200.131:2181,192.168.200.132:2181,192.168.200.133:2181";
    private int sessionTimeout = 2000;
    private ZooKeeper zooKeeper;

    public static void main(String[] args) throws IOException, KeeperException, InterruptedException {

        DistributeServer server = new DistributeServer();
        //获取连接
        server.getConnection();

        //注册服务器到zk集群
        server.register(args[0]);

        //启动业务逻辑(睡觉)
        server.business();
    }

    private void business() throws InterruptedException {
        Thread.sleep(Long.MAX_VALUE);
    }

    private void register(String hostname) throws KeeperException, InterruptedException {
        zooKeeper.create("/servers/" + hostname,hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println(hostname + "已经上线了");
    }

    private void getConnection() throws IOException {
        zooKeeper = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

            }
        });
    }
}

客户端代码

  • 获取zookeeper集群的连接,通过zookeeper的构造函数ZooKeeper(connectString, sessionTimeout, new Watcher(){})
  • 客户端通过监听每个节点,具体监听通过getChildren函数,获取其节点位置,以及是否使用初始化的监听函数,true为使用。获取到的都是以列表存在,输出的时候通过遍历实现,输出的还是一些数组格式。将这些数组都封装到一个列表中,最后统一输出列表即可
  • 执行该函数通过延迟函数

因为注册的时候记录一次
所以在初始化的时候,将其注册放在初始化内部getServerList();

public class DistributeClient {
    private String connectionString = "192.168.200.131:2181,192.168.200.132:2181,192.168.200.133:2181";
    private int sessionTimeout = 2000;
    private ZooKeeper zk;

    public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
        DistributeClient client = new DistributeClient();
        //1.获取zk连接
        client.getConnection();
        //2.监听/servers下面子节点的增加和删除
        client.getServerList();
        //3.业务逻辑(睡觉)
        client.business();
    }

    private void business() throws InterruptedException {
        Thread.sleep(Long.MAX_VALUE);
    }

    private void getServerList() throws KeeperException, InterruptedException {
        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 getConnection() throws IOException {
        zk = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try {
                    getServerList();
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

}

具体测试的逻辑:
启动客户端,通过虚拟机进行create -e -s 节点信息(临时带序号的节点),或者delete进行更新
启动服务器,判定节点是否正常上下线

六、分布式锁

6.1.原生zookeeper分布式锁

创建节点,判断是否是最小的节点
如果不是最小的节点,需要监听前一个的节点

健壮性可以通过CountDownLatch类

监听函数
如果集群状态是连接,则释放connectlatch
如果集群类型是删除,且前一个节点的位置等于该节点的文职,则释放该节点

判断节点是否存在不用一直监听
获取节点信息要一直监听getData

Zookeeper的介绍及使用_第35张图片

public class DistributedLock {

    private final String connectionString = "192.168.200.131:2181,192.168.200.132:2181,192.168.200.133:2181";
    private final int sessionTimeout = 2000;
    private final ZooKeeper zk;
    private CountDownLatch countDownLatch = new CountDownLatch(1);
    private CountDownLatch waitLatch = new CountDownLatch(1);

    private String waitPath;
    private String currentMode;

    public DistributedLock() throws IOException, InterruptedException, KeeperException {

        //获取连接
        zk = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                //connectLatch 如果连接上zk 可以释放
                if(watchedEvent.getState() == Event.KeeperState.SyncConnected){
                    countDownLatch.countDown();
                }

                //waitLatch 需要释放
                if(watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)){
                    waitLatch.countDown();
                }
            }
        });
        //等待zk正常连接后,往下走程序
        countDownLatch.await();
        //判断根节点/locks是否存在
        Stat stat = zk.exists("/locks", false);

        if(stat == null){
            //创建根节点
            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{
                Collections.sort(children);

                //获取节点名称 seq-00000000
                String thisNode = currentMode.substring("/locks/".length());

                //通过seq-00000000获取该节点在children集合的位置
                int index = children.indexOf(thisNode);
//                System.out.println("index:"+index);

                //判断
                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();
        }

    }

}

具体锁的测试文件通过如下测试

public class DistributedLockTest {

    public static void main(String[] args) throws InterruptedException, IOException, KeeperException {
        final DistributedLock lock1 = new DistributedLock();
        final DistributedLock lock2 =new DistributedLock();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                lock1.zkLock();
                System.out.println("线程1 启动,获取到锁");
                    Thread.sleep(5 * 1000);
                    lock1.unZkLock();
                    System.out.println("线程1 释放锁");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    lock2.zkLock();
                    System.out.println("线程2 启动,获取到锁");
                    Thread.sleep(5 * 1000);
                    lock2.unZkLock();
                    System.out.println("线程2 释放锁");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

运行结果
在这里插入图片描述

6.2.curator分布式锁

原生的 Java API 开发存在的问题
(1)会话连接是异步的,需要自己去处理。比如使用CountDownLatch
(2)Watch 需要重复注册,不然就不能生效
(3)开发的复杂性还是比较高的
(4)不支持多节点删除和创建。需要自己去递归

curator是一个专门解决分布式锁的框架,解决了原生Java API开发分布式遇到的问题。

使用

添加依赖

<dependency>
    <groupId>org.apache.curatorgroupId>
    <artifactId>curator-frameworkartifactId>
    <version>4.3.0version>
dependency>
<dependency>
    <groupId>org.apache.curatorgroupId>
    <artifactId>curator-recipesartifactId>
    <version>4.3.0version>
dependency>
<dependency>
    <groupId>org.apache.curatorgroupId>
    <artifactId>curator-clientartifactId>
    <version>4.3.0version>
dependency>

代码实现案例

public class CuratorLockTest {
    public static void main(String[] args) {
        //创建分布式锁1
        InterProcessMutex lock1 = new InterProcessMutex(getCuratorFramework(), "/locks");

        //创建分布式锁2
        InterProcessMutex lock2 = new InterProcessMutex(getCuratorFramework(), "/locks");


        new Thread(new Runnable() {
            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() {
            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() {

        ExponentialBackoffRetry policy = new ExponentialBackoffRetry(3000, 3);

        CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.200.131:2181,192.168.200.132:2181,192.168.200.133:2181")
                .connectionTimeoutMs(2000)
                .sessionTimeoutMs(2000)
                .retryPolicy(policy)
                .build();
        //启动客户端
        client.start();

        System.out.println("zookeeper 启动成功");

        return client;
    }
}

运行结果:
Zookeeper的介绍及使用_第36张图片

七、Zookeeper面试重点

企业中常考的面试有选举机制、集群安装以及常用命令

选举机制
半数机制,超过半数的投票通过,即通过。
(1)第一次启动选举规则:
投票过半数时,服务器 id 大的胜出
(2)第二次启动选举规则:
①EPOCH 大的直接胜出
②EPOCH 相同,事务 id 大的胜出
③事务 id 相同,服务器 id 大的胜出

集群安装

安装奇数台
服务器台数多:好处,提高可靠性;坏处:提高通信延时

常用命令
ls、get、create、delete

你可能感兴趣的:(springcloud,java,开发语言)