Zookeeper3.5.7基础学习

文章目录

  • 一、Zookeeper入门
    • 1、概述
    • 2、特点
    • 3、数据结构
    • 4、应用场景
  • 二、Zookeeper 安装部署
    • 1、本地模式安装
      • 1.1 基础操作
      • 1.2 配置参数解读
    • 2、集群部署
      • 2.1 集群安装
      • 2.2 选举机制(面试重点)
      • 2.3 ZK 集群启动停止脚本
  • 三、ZK客户端相关操作
    • 1、客户端命令行操作
      • 1.1 命令行语法
      • 1.2 znode 节点数据信息
      • 1.3 节点类型(持久/短暂/有序号/无序号)
      • 1.4 节点删除与查看
    • 2、监听器原理
    • 3、客户端 API 操作
    • 4、客户端向服务端写数据流程
  • 四、ZK生产环境案例
    • 1、服务器动态上下线监听案例
      • 1.1 需求分析
      • 1.2 具体实现
      • 1.3 测试与分析
    • 2、ZooKeeper 分布式锁案例
      • 2.1 概述
      • 2.2 原生 Zookeeper 实现分布式锁案例
      • 2.3 Curator 框架实现分布式锁案例
  • 五、总结
    • 1、UI界面安装
    • 2、企业面试真题(面试重点)
      • 2.1 选举机制
      • 2.2 生产集群安装多少 zk 合适?
      • 2.3 常用命令

一、Zookeeper入门

官网:https://zookeeper.apache.org/

1、概述

Zookeeper 是一个开源的分布式的,为分布式框架提供协调服务的 Apache 项目

Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应**。Zookeeper=文件系统+通知机制**

2、特点

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

3、数据结构

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

4、应用场景

提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等

  • 统一命名服务

在分布式环境下,经常需要对应用/服务进行统一命名,便于识别。例如:IP不容易记住,而域名容易记住

  • 统一配置管理

分布式环境下,配置文件同步非常常见。一般要求一个集群中,所有节点的配置信息是一致的,比如 Kafka 集群。对配置文件修改后,希望能够快速同步到各个节点上。配置管理可交由ZooKeeper实现。可将配置信息写入ZooKeeper上的一个Znode;各个客户端服务器监听这个Znode;一旦Znode中的数据被修改,ZooKeeper将通知各个客户端服务器。

  • 统一集群管理

分布式环境中,实时掌握每个节点的状态是必要的。可根据节点实时状态做出一些调整。ZooKeeper可以实现实时监控节点状态变化可将节点信息写入ZooKeeper上的一个ZNode。监听这个ZNode可获取它的实时状态变化。

  • 服务器动态上下线

客户端能实时洞察到服务器上下线的变化

  • 软负载均衡

在Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求

二、Zookeeper 安装部署

1、本地模式安装

1.1 基础操作

# 首先自行安装jdk,这里我就继续在之前的hadoop集群上继续操作了
wget https://archive.apache.org/dist/zookeeper/zookeeper-3.5.7/apache-zookeeper-3.5.7-bin.tar.gz
tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/module/
cd /opt/module/
mv apache-zookeeper-3.5.7-bin/ zookeeper-3.5.7/

# ============配置修改=============
# 将/opt/module/zookeeper-3.5.7/conf 这个路径下的 zoo_sample.cfg 修改为 zoo.cfg
mv zoo_sample.cfg zoo.cfg
# 打开 zoo.cfg 文件,修改 dataDir 路径
vim zoo.cfg
# 修改如下内容
dataDir=/opt/module/zookeeper-3.5.7/zkData
# 在/opt/module/zookeeper-3.5.7/这个目录上创建 zkData 文件夹
mkdir zkData

# ============操作 Zookeeper============
# 启动 Zookeeper
bin/zkServer.sh start
# 查看进程是否启动
jps
# 查看状态
bin/zkServer.sh status
# 启动客户端
bin/zkCli.sh
quit
# 停止 Zookeeper
bin/zkServer.sh stop

1.2 配置参数解读

vim zoo.cfg

# 通信心跳时间,Zookeeper服务器与客户端心跳时间,单位毫秒
tickTime=2000
# LF初始通信时限,Leader和Follower初始连接时能容忍的最多心跳数(tickTime的数量)
# 指定了Zookeeper集合中的Follower节点(从节点)在连接到Leader节点(主节点)时能够等待的时间量。在这个时间范围内,如果从节点不能连接到Leader节点,则从节点将放弃连接尝试
initLimit=10
# LF同步通信时限
# Leader和Follower之间通信时间如果超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer
syncLimit=5
# 保存Zookeeper中的数据
# 注意:默认的tmp目录,容易被Linux系统定期删除,所以一般不用默认的tmp目录。
dataDir=/opt/module/zookeeper-3.5.7/zkData
# 客户端连接端口,通常不做修改
clientPort=2181

2、集群部署

2.1 集群安装

# 在 hadoop102、hadoop103 和 hadoop104 三个节点上都部署 Zookeeper(这是之前hadoop集群用的)
# 在 hadoop102 解压 Zookeeper 安装包到/opt/module/目录下
tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/module/
cd /opt/module/
mv apache-zookeeper-3.5.7-bin/ zookeeper-3.5.7/

# ============配置修改=============
# 在/opt/module/zookeeper-3.5.7/这个目录上创建 zkData 文件夹
mkdir zkData
# 在/opt/module/zookeeper-3.5.7/zkData 目录下创建一个 myid 的文件
# 在文件中添加与 server 对应的编号(注意:上下不要有空行,左右不要有空格)
# 这里编写一个2,注意:添加 myid 文件,一定要在 Linux 里面创建,在 notepad++里面很可能乱码
vi myid

# 拷贝配置好的 zookeeper 到其他机器上,分发脚本可以想见之前hadooop3.x学习笔记文章
xsync zookeeper-3.5.7
# 并分别在 hadoop103、hadoop104 上修改 myid 文件中内容为 3、4

# ============配置zoo.cfg文件============
# 重命名/opt/module/zookeeper-3.5.7/conf 这个目录下的 zoo_sample.cfg 为 zoo.cfg
mv zoo_sample.cfg zoo.cfg
vim zoo.cfg
# 修改数据存储路径配置
dataDir=/opt/module/zookeeper-3.5.7/zkData
# 增加如下配置
#######################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,而这个端口就是用来执行选举时服务器相互通信的端口。

# 同步zoo.cfg 配置文件
xsync zoo.cfg

# ====================集群操作====================
# 分别三台机器启动Zookeeper
bin/zkServer.sh start
# 查看状态
bin/zkServer.sh status

2.2 选举机制(面试重点)

2.3 ZK 集群启动停止脚本

在 hadoop102 的/home/atguigu/bin 目录下创建脚本: vim zk.sh

#!/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
# 增加脚本执行权限
chmod u+x zk.sh
zk.sh start
zk.sh stop

三、ZK客户端相关操作

1、客户端命令行操作

1.1 命令行语法

# 启动客户端
bin/zkCli.sh -server hadoop102:2181
# 显示所有操作命令
help

命令基本语法功能描述
help显示所有操作命令
ls path使用 ls 命令来查看当前 znode 的子节点 [可监听] -w 监听子节点变化 -s 附加次级信息
create普通创建 -s 含有序列 -e 临时(重启或者超时消失)
get path获得节点的值 [可监听] -w 监听节点内容变化 -s 附加次级信息
set设置节点的具体值
stat查看节点状态
delete删除节点
deleteall递归删除节点

1.2 znode 节点数据信息

# 查看当前znode中所包含的内容
ls / 
# 查看当前节点详细数据
ls -s /

  • czxid:创建节点的事务 zxid。每次修改ZooKeeper 状态都会产生一个ZooKeeper 事务 ID。事务 ID 是ZooKeeper 中所有修改总的次序。每次修改都有唯一的 zxid,如果 zxid1 小于 zxid2,那么zxid1 在 zxid2 之前发生。
  • ctime:znode 被创建的毫秒数(从 1970 年开始)
  • mzxid:znode 最后更新的事务zxid
  • mtime:znode 最后修改的毫秒数(从 1970 年开始)
  • pZxid:znode 最后更新的子节点zxid
  • cversion:znode 子节点变化号,znode 子节点修改次数
  • dataversion:znode 数据变化号
  • aclVersion:znode 访问控制列表的变化号
  • ephemeralOwner:如果是临时节点,这个是 znode 拥有者的 session id。如果不是临时节点则是 0
  • dataLength:znode 的数据长度
  • numChildren:znode 子节点数量

1.3 节点类型(持久/短暂/有序号/无序号)

# 分别创建2个普通节点(永久节点 + 不带序号)
# 注意:创建节点时,要赋值
create /sanguo "diaochan"
create /sanguo/shuguo "liubei"
# 获得节点的值
get -s /sanguo
get -s /sanguo/shuguo

# 创建带序号的节点(永久节点 + 带序号)
# 先创建一个普通的根节点/sanguo/weiguo
create /sanguo/weiguo "caocao"
# 创建带序号的节点
create -s /sanguo/weiguo/zhangliao "zhangliao"
# 重复编号会增加
create -s /sanguo/weiguo/zhangliao "zhangliao"

# 创建短暂节点(短暂节点 + 不带序号 or 带序号)
# 创建短暂的不带序号的节点
create -e /sanguo/wuguo "zhouyu"
# 创建短暂的带序号的节点
create -e -s /sanguo/wuguo "zhouyu"
# 在当前客户端是能查看到的
ls /sanguo
# 退出当前客户端然后再重启客户端
quit
bin/zkCli.sh
# 再次查看根目录下短暂节点已经删除
ls /sanguo

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

1.4 节点删除与查看

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

2、监听器原理

客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、节点删除、子目录节点增加删除)时,ZooKeeper 会通知客户端。监听机制保证 ZooKeeper 保存的任何的数据的任何改变都能快速的响应到监听了该节点的应用程序

# 节点的值变化监听
# 在 hadoop104 主机上注册监听/sanguo 节点数据变化
get -w /sanguo
# 在 hadoop103 主机上修改/sanguo 节点的数据
# 观察 hadoop104 主机收到数据变化的监听
set /sanguo "xisi"
# 注意:在hadoop103再多次修改/sanguo的值,hadoop104上不会再收到监听。因为注册一次,只能监听一次。想再次监听,需要再次注册

# 节点的子节点变化监听(路径变化)
# 在 hadoop104 主机上注册监听/sanguo 节点的子节点变化
ls -w /sanguo
# 在 hadoop103 主机/sanguo 节点上创建子节点
# create /sanguo/jin "simayi"
create /sanguo/jin "simayi"

3、客户端 API 操作

前提:保证 hadoop102、hadoop103、hadoop104 服务器上 Zookeeper 集群服务端启动

创建zookeeper工程,引入对应依赖

<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>

需要在项目的 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 

创建包名com.atguigu.zk,创建类名称zkClient

public class zkClient {

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

    //创建 ZooKeeper 客户端
    @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);
//
//                    for (String child : children) {
//                        System.out.println(child);
//                    }
//
//                    System.out.println("-------------------------------");
//                } catch (KeeperException e) {
//                    e.printStackTrace();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }
        });
    }
    
    //创建子节点
    @Test
    public void create() throws KeeperException, InterruptedException {
        // 参数 1:要创建的节点的路径; 参数 2:节点数据 ; 参数 3:节点权限 ;参数 4:节点的类型
        String nodeCreated = zkClient.create("/atguigu", "ss.avi".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);
    }

    @Test
    public void exist() throws KeeperException, InterruptedException {
        // 判断 Znode 是否存在
        Stat stat = zkClient.exists("/atguigu", false);
        System.out.println(stat==null? "not exist " : "exist");
    }
}

4、客户端向服务端写数据流程

写流程之写入请求直接发送给Leader节点(写完半数即可返回成功)

写流程之写入请求发送给follower节点

四、ZK生产环境案例

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

1.1 需求分析

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

1.2 具体实现

首先在集群上创建/serves节点 create /servers "servers"

在 Idea 中创建包名:com.atguigu.zkcase1,创建服务端DistributeServer

public class DistributeServer {

    private String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
    private int sessionTimeout = 2000;
    private ZooKeeper zk;

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

        DistributeServer server = new DistributeServer();
        // 1 获取zk连接
        server.getConnect();

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


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

    }

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

    private void regist(String hostname) throws KeeperException, InterruptedException {
        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) {

            }
        });
    }
}

创建客户端DistributeClient

public class DistributeClient {

    private String connectString = "hadoop102:2181,hadoop103:2181,hadoop104: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.getConnect();

        // 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 getConnect() throws IOException {
        zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

                try {
                    getServerList();
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

1.3 测试与分析

# 首先启动客户端
# 然后可以进行命令行的测试
create -e -s /servers/hadoop102 "hadoop102"


# 最后启动服务端,这里需要进行设置,否则同一时刻只能启动一个
# 点击 Edit Configurations,在弹出的窗口中(Program arguments)输入想启动的主机,例如,hadoop102
# 然后进行启动注册

2、ZooKeeper 分布式锁案例

2.1 概述

分布式锁的概念可以参考:几种分布式锁详解

2.2 原生 Zookeeper 实现分布式锁案例

public class DistributedLock {

    private final String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
    private final int sessionTimeout = 2000;
    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) {
            // 创建一下根节点
            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);

            // wait一小会, 让结果更清晰一些
            Thread.sleep(10);

            // 判断创建的节点是否是最小的序号节点,如果是获取到锁;如果不是,监听他序号前一个节点

            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);

                // 判断
                if (index == -1) {
                    System.out.println("数据异常");
                } else if (index == 0) {
                    // 就一个节点,可以获取锁了
                    return;
                } else {
                    // 需要监听  他前一个节点变化
                    waitPath = "/locks/" + children.get(index - 1);
                    zk.getData(waitPath,true,new Stat());

                    // 等待监听
                    waitLatch.await();

                    return;
                }
            }


        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }

    // 解锁
    public void unZkLock() {

        // 删除节点
        try {
            zk.delete(this.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();

    }
}

2.3 Curator 框架实现分布式锁案例

https://curator.apache.org/index.html

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

Curator 是一个专门解决分布式锁的框架,解决了原生 JavaAPI 开发分布式遇到的问题,首先添加依赖

<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>

代码实现

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() {
            @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() {

        ExponentialBackoffRetry policy = new ExponentialBackoffRetry(3000, 3);

        CuratorFramework client = CuratorFrameworkFactory.builder().connectString("hadoop102:2181,hadoop103:2181,hadoop104:2181")
                .connectionTimeoutMs(2000)
                .sessionTimeoutMs(2000)
                .retryPolicy(policy).build();

        // 启动客户端
        client.start();

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

五、总结

1、UI界面安装

参考:Centos7安装zookeeper和Web UI

2、企业面试真题(面试重点)

2.1 选举机制

半数机制,超过半数的投票通过,即通过

第一次启动选举规则:

  • 投票过半数时,服务器 id 大的胜出

第二次启动选举规则:

  • EPOCH 大的直接胜出
  • EPOCH 相同,事务 id 大的胜出
  • 事务 id 相同,服务器 id 大的胜出

2.2 生产集群安装多少 zk 合适?

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

  • 10 台服务器:3 台 zk;
  • 20 台服务器:5 台 zk;
  • 100 台服务器:11 台 zk;
  • 200 台服务器:11 台 zk

2.3 常用命令

ls、get、create、delete

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/347129.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

车载显示,“激斗”与“换代”

编者按&#xff1a;车载显示&#xff0c;正在进入新一轮变革周期。 车载显示作为汽车智能化的重要交互终端&#xff0c;在过去几年&#xff0c;持续受益车企的大屏化、多屏化配置趋势&#xff0c;部分头部厂商赚得盆满钵满。 比如&#xff0c;作为京东方旗下唯一的车载显示模组…

携程基于Jira Cloud的敏捷项目管理实践

好的工具可以满足团队在各个成长阶段的管理诉求 实践一&#xff1a;对齐目标/团队OKR/多团队协作战略项目 实践二&#xff1a;以产品为中心的协作框架 实践三&#xff1a;交付团队管理 实践四&#xff1a;和海外子公司对齐&#xff0c;协作

guitarpro8有免费版本吗? Guitar Pro需要多少钱

Guitar Pro8官方并没有提供一个完全免费的版本。然而&#xff0c;根据搜索结果&#xff0c;存在一个声称是Guitar Pro免费版的V8.0.1.28版本&#xff0c;这个版本被描述为一款可以自动谱曲的简单实用的谱曲软件。此外&#xff0c;还有信息称Guitar Pro免费版能够在不需要支付费…

在linux、window环境搭建kafka环境

一、搭建环境前置准备 下载kafka的官网 http://kafka.apache.org/downloads根据自己的需求选择版本,安装包不区分linux和windows环境,这一个安装包均可部署。 源代码包含kafka的代码文件,使用scala编写的。 二、linux环境 1. 上传安装包 我下载的版本是kafka_2.12-3.6.1…

php中laravel框架中接口开发实战经验总结

一.项目接口实战错误经验总结 1. 逻辑层静态类调用非静态方法报错&#xff0c;如下 Non-static method App\\Services\\Common\\StatisticBusinessService::getLevelUserCarCount() should not be called statically 问题原因:方法定义时没加static 解决方案&#xff1a; …

Threejs 问题——模型引入后并未加载出来

文章目录 问题分析 问题 Threejs 模型引入后并未加载出来 分析 该问题是由于引入OBJ模型路径报错 目前测试 在 public/static/ 引入时不会发生此错误并渲染成功 如果没有此报错且模型没渲染出来, 可以尝试刷新标签页或者异步请求模型方法 我的路径如下 引用如下 createM…

【2024】新建mysql数据库,如何选择字符集和排序规则

如何使用 Navicat 新建 MySQL 数据库&#xff0c;并选择字符集与排序规则 如何使用 Navicat 新建 MySQL 数据库并选择字符集与排序规则1. 开始之前2. 新建数据库步骤 1: 打开 Navicat步骤 2: 创建新数据库步骤 3: 填写数据库名称 常见的字符集和排序规则及其选择场景1. 字符集&…

MCU常用外设总线

目录 前言一、时钟与中断二、GPIO三、ADC四、定时器4.1 基本定时器4.2 通用定时器4.2.1 输入捕获4.2.2 输出比较 五、UART5.1 通讯的基本概念5.1.1 串行通讯与并行通讯5.1.2 全双工、半双工及单工通讯5.1.3 同步通讯与异步通讯5.1.4 通信速率 5.2 异步串口UART5.2.1 物理层5.2.…

谷达冠楠:现在开网店到底靠谱吗

在互联网高速发展的当下&#xff0c;开网店已成为众多创业者的选择。然而&#xff0c;面对激烈的市场竞争和不断变化的网络环境&#xff0c;人们不禁要问&#xff1a;现在开网店到底靠谱吗? 我们必须认识到&#xff0c;任何商业行为都存在风险&#xff0c;开网店也不例外。但是…

Python + Selenium —— 网页元素定位之标签名和链接文本定位

tag name tag name 为标签名定位&#xff0c;使用网页元素的标签名如a, div, input, span 等。 但是有一个问题&#xff0c;常见的标签名比如 在同一个页面上有非常多。会不会觉得 tag name 没什么用呢&#xff1f; 当然普通的模拟操作是不大有用&#xff0c;这个重复性实在…

Spring如何使用自定义注解来实现自动管理事务?

人可以做他(她)想做的&#xff0c;但不能要他(她)想要的 一个目录 前言业务代码展示手动挡自动挡事务失效的问题代码地址 前言 在两年半以前&#xff0c;我写了一篇博客&#xff1a;框架的灵魂之注解基础篇&#xff1a; 在那篇博客的结尾&#xff0c;我埋了一个坑&#xff1a…

mac安装部署gitbook教程

mac安装部署gitbook教程 前言一、安装准备二、GitBook安装三、项目初始化 前言 一些自己实际操作的记录。 一、安装准备 Node.js gitbook基于Node.js&#xff0c;所以需要提前安装。 下载地址&#xff1a;https://nodejs.org/en/&#xff0c;可以下载比较新的版本。(但我的建议…

深入到 TLP:PCI Express 设备如何通信

前言 当我为PCI express编写Xillybus IP核时&#xff0c;我很快发现很难开始&#xff1a;在线资源和官方规格用关于螺母和螺栓的血腥细节轰炸你&#xff0c;但对机器应该做什么却很少说。因此&#xff0c;一旦我努力自己弄清楚这一点&#xff0c;我就决定写这个小指南&#xf…

源码篇--Redis 五种数据类型

文章目录 前言一、 字符串类型&#xff1a;1.1 字符串的编码格式&#xff1a;1.1.1 raw 编码格式:1.1.2 empstr编码格式:1.1.3 int 编码格式:1.1.4 字符串存储结构展示: 二、 list类型&#xff1a;2.1 List 底层数据支持&#xff1a;2.2 List 源码实现&#xff1a;2.3 List 结构…

【C++】反向迭代器模拟实现

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.利用适配器的思想…

【产品设计】B端导航菜单的三大模式

导航是每一个网站的灵魂所在&#xff0c;用户依赖导航进行不同页面的切换&#xff0c;找到自己所需的。那么该如何将庞大的B端系统中的导航菜单做好呢&#xff1f; 导航菜单是一个网站的灵魂&#xff0c;用户依赖导航在各个页面中进行跳转。 导航菜单一般分为顶部导航和侧边导…

成本更低、更可控,云原生可观测新计费模式正式上线

云布道师 在上云开始使用云产品过程中&#xff0c;企业一定遇见过两件“讨厌”事&#xff1a; 难以理解的复杂计费逻辑&#xff0c;时常冒出“这也能收费”的感叹&#xff1b; 某个配置参数调节之后&#xff0c;云产品使用成本不可预估的暴涨。 可观测作为企业 IT 运维必须品…

Python工具:pathlib

文件的路径实际上是一件很困扰的时间&#xff08;各种平台有时候规则不一样&#xff0c;有时候还需要考虑字符转义的问题&#xff09;&#xff0c;因此我直接推荐使用模块 pathlib&#xff0c;当然&#xff0c;如果您不介意的话&#xff0c;可以使用 os.path 做较为低级的路径操…

SpringCloudAlibaba系列之Nacos实战

目录 注意事项 参考资源 Nacos配置中心 初始化项目 进行Nacos相关配置 运行 Nacos注册中心 dubbo方式 对外暴露接口dubbo-api 服务提供者dubbo-provider 服务消费者dubbo-consumer 负载均衡客户端方法 服务提供者 服务消费者 注意事项 不管是使用Nacos配置中心&…

程序员手把手教你参与开源!拿捏!

一、前言 有一些同学提问&#xff0c;希望在自己的简历上增加一些有含金量的项目经历&#xff0c;最好能够去参与一些开源项目的开发&#xff0c;但由于对一个庞大的开源项目缺乏认知&#xff0c;难以着手。同时也担心自己能力不足&#xff0c;不知道自己写的代码是否会被接纳。…