Zookeeper

Zookeeper

1. Zookeeper 简介

Zookeeper官网

  • Zookeeper 是 Apache Hadoop 项目下的一个子项目,是一个树形目录服务
  • Zookeeper 是一个分布式的、开源的分布式应用程序的协调服务
  • Zookeeper 提供的主要功能包括
    • 配置中心:Zookeeper可以存储、管理和跟踪系统配置,从而简化了系统管理。
    • 同步:Zookeeper可以实现分布式系统中的数据一致性,从而使分布式系统更加可靠。
    • 命名服务:Zookeeper可以提供一致的、可靠的、分布式的命名服务,从而简化了分布式系统的管理。
    • 集群管理:Zookeeper可以实现集群管理,从而简化了集群管理的复杂性。
    • 共享锁定:Zookeeper可以实现共享锁定,从而简化了分布式系统中的数据同步。

2. Zookeeper 安装

  • 下载后进行解压
tar zxvf apache-zookeeper-x.x.x-bin.tar.gz
  • 进入安装目录中的 conf 目录,处理配置文件
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg

在这里插入图片描述

在这里插入图片描述

  • 启动 查看 重启 关闭 zookeeper 所在目录~/apache-zookeeper-3.7.0-bin/bin
sudo ./zkServer.sh start 		# 开启服务
sudo bash zkServer.sh start
sudo ./zkServer.sh status		# 查看状态
sudo ./zkServer.sh restart		# 重启服务
sudo ./zkServer.sh stop			# 关闭服务
  • 查看服务器进程
ps -ef | grep zookeeper

3. Zookeeper 操作指令

3.1 数据模型

  • ZooKeeper 是一个树形目录服务,其数据模型和 Unix 的文件系统目录树很类似,拥有一个层次化结构
    在这里插入图片描述
  • 每一个节点都被称为: ZNode,每个节点上都会保存自己的数据和节点信息
  • 节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下
  • 节点可以分为四大类
    1. 持久化节点
    2. 持久化顺序节点-s
    3. 临时节点 -e
    4. 临时顺序节点 -es

3.2 客户端操作指令

命令说明
./zkCli.sh -server localhost:2181通过客户端连接服务器
quit退出客户端
create <节点名> [值]创建节点
get <节点名>获取节点中的值
set <节点名> <值>修改节点中的值
delete <空节点名>删除空节点
deleteall <节点名>删除任意节点(非空也可以)
ls <节点名>查看节点中包含的所有子节点
help帮助命令

4 - Zookeeper JavaAPI

4.1 - Curator API

Curator 是 Apache ZooKeeper 的 Java 客户端 API

4.2 - Curator 的常用操作

  • pom.xml

    <dependencies>
    
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
        </dependency>
    
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>5.2.0</version>
        </dependency>
    
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>5.2.0</version>
        </dependency>
    
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.33</version>
        </dependency>
    
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.33</version>
        </dependency>
    
    </dependencies>
    
  • log4j.properties

  log4j.rootLogger=off,stdout
  log4j.appender.stdout=org.apache.log4j.ConsoleAppender
  log4j.appender.stdout.Target=System.out
  log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
  • Test.java
package com.itxw;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

/**
 * @Classname Test
 * @Description: TODO
 * @Author: User
 */
public class Test {
    CuratorFramework client;

    //连接ZooKeeper
    @Before
    public void curatorConnect() {
  
        //定义重试的策略
        ExponentialBackoffRetry retry = new ExponentialBackoffRetry(5000, 10);

        //第一种连接方式:通过 newClient
        // CuratorFrameworkFactory.newClient("127.0.0.1:2181",retry);

        //第二种连接方式
        //获取连接对象
        client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181") // 连接Ip
                .retryPolicy(retry) // 重试策略
                .namespace("itxw") //当前连接的命名空间
                .build(); //构建对象
        //********所有的path都是以命名空间开始********
        // 启动客户端并且建立连接
        client.start();
    }

    @After
    //断开ZooKeeper连接
    public void curatorClose() {
        client.close();
    }

    //=============================添加节点================================

    @Test
    //创建一个空的结点
    public void createTest01() throws Exception {
        // 在创建空节点的时候,里面的值实际上不是空的,Curator 会为节点填充网络的 IP 地址
        String path = client.create().forPath("/lesson");
        System.out.println("成功创建了:" + path);
    }

    @Test
    //创建一个带值的结点
    public void createTest02() throws Exception {
        String path = client.create().forPath("/search", "ZooKeeper".getBytes());
        System.out.println("成功创建了:" + path);
    }

    @Test
    //创建一个临时的结点
    public void createTest03() throws Exception {
        String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/lesson", "ZooKeeper".getBytes());
        System.out.println("成功创建了:" + path);
        while (true) ;//加入一个循环,不会执行到Close,可以查看到创建到的内容
    }

    @Test
    // 一次性创建多层节点
    public void createTest04() throws Exception {
        String path = client.create().creatingParentContainersIfNeeded().forPath("/lesson/java/thread", "ZooKeeper".getBytes());
        System.out.println("成功创建了:" + path);
    }

    //=============================查询节点================================

    @Test
    //查询指定节点的值
    public void getTest01() throws Exception {
        byte[] info = client.getData().forPath("/search");
        System.out.println("info = " + new String(info));
    }

    @Test
    //查询子节点
    public void getTest02() throws Exception {
        List<String> pathList = client.getChildren().forPath("/");
        for (String path : pathList) {
            System.out.println(path);
        }
    }

    @Test
    //查询节点的状态
    public void getTest03() throws Exception {
        Stat stat = new Stat();
        byte[] path = client.getData().storingStatIn(stat).forPath("/");
        System.out.println("path = " + new String(path));
        System.out.println("================================");
        System.out.println(stat);
        System.out.println(stat.getCzxid());
        System.out.println(stat.getMzxid());
        System.out.println(stat.getCtime());
        System.out.println(stat.getMtime());
    }

    //=============================修改节点================================

    @Test
    //修改节点
    public void setTest01() throws Exception {
        Stat stat = client.setData().forPath("/lesson", "Curator".getBytes());
        System.out.println("stat = " + stat);
        System.out.println("================================");
        byte[] bytes = client.getData().forPath("/lesson");
        System.out.println("info = " + new String(bytes));

    }

    @Test
    // 根据版本号 dataVersion 进行修改
    public void setTest02() throws Exception {
        Stat stat = new Stat();
        //获取状态信息
        client.getData().storingStatIn(stat).forPath("/lesson");
        Stat stat1 = client.setData().withVersion(stat.getVersion()).forPath("/lesson", "CuratorAPI".getBytes());
        System.out.println("stat = " + stat1);
        System.out.println("================================");
        byte[] bytes = client.getData().forPath("/lesson");
        System.out.println("info = " + new String(bytes));
    }

    //=============================删除节点================================

    @Test
    //删除单个节点
    public void delTest01() throws Exception {
        client.delete().forPath("/lesson");
    }

    @Test
    //删除带有子节点的节点
    public void delTest02() throws Exception {
        client.delete().deletingChildrenIfNeeded().forPath("/lesson");
    }

    @Test
    //确保成功删除节点
    public void delTest03() throws Exception {
        client.delete().guaranteed().forPath("/lesson");
    }

    @Test
    //删除动作之后,调用一个回调函数
    public void delTest04() throws Exception {
        client.delete().guaranteed().inBackground(new BackgroundCallback() {
            @Override
            public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
                System.out.println("删完了!");
                System.out.println(curatorEvent.getPath() + "被删除了");
            }
        }).forPath("/search");
    }

    //=============================事件监听================================

    @Test
    // NodeCache:监听某一个指定的节点
    public void nodeCacheTest() throws Exception {
        // 创建 NodeCache 对象
        final NodeCache nodeCache = new NodeCache(client, "/lesson");
        // 注册监听器
        nodeCache.getListenable().addListener(new NodeCacheListener() {
            public void nodeChanged() throws Exception {
                System.out.println("/lesson 中的内容发生变化了");
                byte[] info = nodeCache.getCurrentData().getData();
                System.out.println(new String(info));
            }
        });
        // 开启监听
        nodeCache.start(true); // true 表示开启监听的时候就加载缓存,false 则相反
        // 保持运行
        while (true);
    }

    @Test
    //PathChildrenCache:监听一个 ZNode 的子节点
    public void pathChildrenCacheTest() throws Exception {
        // 创建 PathChildrenCache 对象
        PathChildrenCache pathChildrenCache = new PathChildrenCache(client, "/lesson", true);
        // 注册监听  这个监听器是PathChildrenCacheListener的一个实例
        pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
            public void childEvent(CuratorFramework curatorFramework,
                                   PathChildrenCacheEvent event) throws Exception {
                if(event.getType() == PathChildrenCacheEvent.Type.CHILD_UPDATED){
                    System.out.println("子节点更新");
                    System.out.println("节点:"+event.getData().getPath());
                    System.out.println("数据" + new String(event.getData().getData()));
                }else if(event.getType() == PathChildrenCacheEvent.Type.INITIALIZED ){
                    System.out.println("初始化操作");
                }else if(event.getType() == PathChildrenCacheEvent.Type.CHILD_REMOVED ){
                    System.out.println("删除子节点");
                    System.out.println("节点:"+event.getData().getPath());
                    System.out.println("数据" + new String(event.getData().getData()));
                }else if(event.getType() == PathChildrenCacheEvent.Type.CHILD_ADDED ){
                    System.out.println("添加子节点");
                    System.out.println("节点:"+event.getData().getPath());
                    System.out.println("数据" + new String(event.getData().getData()));
                }else if(event.getType() == PathChildrenCacheEvent.Type.CONNECTION_SUSPENDED ){
                    System.out.println("连接失效");
                }else if(event.getType() == PathChildrenCacheEvent.Type.CONNECTION_RECONNECTED ){
                    System.out.println("重新连接");
                }else if(event.getType() == PathChildrenCacheEvent.Type.CONNECTION_LOST ){
                    System.out.println("连接失效后稍等一会儿执行");
                }
            }
        });
        // 开启监听
        pathChildrenCache.start();
        // 保持运行
        System.in.read();
    }

    @Test
    // TreeCache:监听整个树形结构的所有节点
    public void treeCacheTest() throws Exception {
        // 创建 TreeCache 对象
        TreeCache treeCache = new TreeCache(client,"/");
        // 注册监听
        treeCache.getListenable().addListener(new TreeCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
                if(event.getType() == TreeCacheEvent.Type.NODE_ADDED){
                    System.out.println(event.getData().getPath() + "节点添加");
                }else if (event.getType() == TreeCacheEvent.Type.NODE_REMOVED){
                    System.out.println(event.getData().getPath() + "节点移除");
                }else if(event.getType() == TreeCacheEvent.Type.NODE_UPDATED){
                    System.out.println(event.getData().getPath() + "节点修改");
                    System.out.println(new String(event.getData().getData()));
                }else if(event.getType() == TreeCacheEvent.Type.INITIALIZED){
                    System.out.println("初始化完成");
                }else if(event.getType() ==TreeCacheEvent.Type.CONNECTION_SUSPENDED){
                    System.out.println("连接过时");
                }else if(event.getType() ==TreeCacheEvent.Type.CONNECTION_RECONNECTED){
                    System.out.println("重新连接");
                }else if(event.getType() ==TreeCacheEvent.Type.CONNECTION_LOST){
                    System.out.println("连接过时一段时间");
                }
            }
        });
        // 启动监听
        treeCache.start();
        // 阻塞
        System.in.read();
    }
    
}

分布式锁

跨设备的进程之间的数据同步锁

  • 分布式锁的实现方式
    • 基于缓存redis来实现(性能高,不可靠)
    • 通过数据库实现(效率低,不推荐)
    • Zookeeper 实现(相对平衡)
  • Zookeeper 分布式锁原理
    • 客户端获取锁时,在 lock 节点下创建 临时的顺序 节点(1.通过序号来取 2.临时的可以避免死锁的情况,比如浏览器界面卡了,如果采用持久的那么一直会不释放,出现死锁)
    • 然后获取 lock 下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小,就说明当前客户端获取到了锁,使用完成之后,释放锁就是删除这个节点。
    • 如果发现自己创建的节点并非 lock 所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册监听器,监听删除事件。如果发现删除了。说明上一个锁已经释放了
    • 当发现上一个释放了之后。再次判断自己是不是最小的。如果是我就是获取到了。如果不是重复以上步骤
  • Curator 分布式锁相关 API
说明
InterProcessSemaphoreMutex分布式排它锁(非可重入锁)
InterProcessMutex分布式可重入排它锁
InterProcessReadWriteLock分布式读写锁
InterProcessMultiLock将多个锁作为单个实体管理的容器
InterProcessSemaphoreV2共享信号量

5 - Zookeeper 集群搭建

5.1 - 集群相关角色

  • Leader 领导者:处理事务并同步到其他的 Follower
  • Follower 跟随者:处理非事务请求,如果有事务请求将转发给 Leader,参与投票
  • Observer 观察者:不参与投票

5.2 - 集群搭建步骤

  1. 修改配置文件

    cp /home/itxw/apache-zookeeper-3.7.0-bin/conf/zoo_sample.cfg /home/itxw/apache-zookeeper-3.7.0-bin/conf/zoo.cfg
    
    # 修改数据默认存放目录
    dataDir=/home/itxw/zookeeper-cluster/zookeeper-1/data
     # 最底部添加集群配置
    # server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口
    server.1=192.168.1.88:29527:39527
    server.2=192.168.1.88:29528:39528
    server.3=192.168.1.88:29529:39529
    
  2. 执行统一配置脚本zookeeperCluster.sh

    #!/bin/bash
    #create zookeeper-cluster
    
    # 创建集群配置目录
    mkdir /home/itxw/zookeeper-cluster
    
    # 拷贝独立的 zookeeper 到集群中备用
    cp -r /home/itxw/apache-zookeeper-3.7.0-bin /home/itxw/zookeeper-cluster/zookeeper-1
    cp -r /home/itxw/apache-zookeeper-3.7.0-bin /home/itxw/zookeeper-cluster/zookeeper-2
    cp -r /home/itxw/apache-zookeeper-3.7.0-bin /home/itxw/zookeeper-cluster/zookeeper-3
    
    # 写入id
    echo 1 >/home/itxw/zookeeper-cluster/zookeeper-1/data/myid
    echo 2 >/home/itxw/zookeeper-cluster/zookeeper-2/data/myid
    echo 3 >/home/itxw/zookeeper-cluster/zookeeper-3/data/myid
    
    # 修改配置文件中的端口号
    sed -i "s/2181/2182/g" /home/itxw/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
    sed -i "s/2181/2183/g" /home/itxw/zookeeper-cluster/zookeeper-3/conf/zoo.cfg
    
    # 修改配置文件中的 data 目录
    sed -i "s/zookeeper-1/zookeeper-2/g" /home/itxw/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
    sed -i "s/zookeeper-1/zookeeper-3/g" /home/itxw/zookeeper-cluster/zookeeper-3/conf/zoo.cfg
    
    
    #启动
    zookeeper-cluster/zookeeper-1/bin/zkServer.sh start
    zookeeper-cluster/zookeeper-2/bin/zkServer.sh start
    zookeeper-cluster/zookeeper-3/bin/zkServer.sh start
    ###如果修改后启动不成功,记得restart
    #查看状态
    zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
    zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
    zookeeper-cluster/zookeeper-3/bin/zkServer.sh status
    

    在这里插入图片描述

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值