第六节 Zookeeper 分布式应用程序协调服务

Zookeeper

1. Zookeeper干啥的?

  1. 在之前搭建Hadoop HA的集群搭建中,我们引入了该组件,可以得知其中一种作用在于监听NameNode的健康状况。那它还有其他的作用么 ?
  2. 它属于 分布式 应用程序 协调服务 框架,是一个开源,是Hadoop和Hbase重要的组件,这里面有两个关键点,一个是分布式? 一个是协调服务?
    • 思考: 多台机器在一个网络中就是分布式么?
    • 什么又是协调服务 ?
      在这里插入图片描述
  3. Zookeeper所提供的服务涵盖: 主从协调, 服务器节点上下线感知,统一配置管理, 分布式共享锁, 统一名称服务等
    Zookeeper 两个主要的功能:
    • 一, 管理(存储,读取) 用户提交的数据。
    • 二, 为用户程序提供节点监听服务

2. Zookeeper运用场景

2.1 主从协调

  1. 一个master 节点(active ) 与 另一个maser节点(standby)之间的状态之间切换。

在这里插入图片描述

2.2 选举Master节点

  1. 当一个Master 节点宕机之后 ,从多个Slave 节点如何选举出Master保持继续工作。

在这里插入图片描述

2.3 分布式共享锁

  1. 保证各节点有序获取数据, Zookeeper有序目录的创建和删除 机制。

在这里插入图片描述

3. Zookeeper 安装前准备与配置

3.1 安装前Linux配置工作

  1. 同安装Hadoop一样,安装前需要配置Linux环境,可以参考 第5小节 Linux的基本配置
    • 关闭防火墙,
    • 配置hosts (ip地址+主机名称)
    • 配置免密钥
    • 修改主机名称
    • 修改主机ip地址为固定(查看虚拟机网络ipNat模式)
    • 配置java环境变量
  2. 配置完成之后,上传zookeeper的解压程序到Linux上 apache-zookeeper-3.6.0-bin.tar.gz 安装程序

3.2 克隆节点

  1. 安装前配置好之后,开始克隆节点,需要在关闭虚拟机状态下进行操作,可以参照 第3.3 克隆节点
    • 克隆 2台机器即可,然后注意的是修改,主机名称,主机ip,相互发送密钥ssh-copy-id root@ ip地址
    • 远程发送文件 scp myid root@hd02:/usr/zkdata/
    • 远程发送目录scp -r /usr/java/apache-zookeeper-3.6.0-bin root@hd02:/usr/java/

3.3 Zookeeper 配置

3.3.1 修改 Conf 目录
  1. 进入Zookeeper /usr/java/apache-zookeeper-3.6.0-bin/conf 目录下,将配置文件 zoo_sample.cfg 复制一份修改为 zoo.cfg
    • 命令: cp zoo_sample.cfg zoo.cfg
      在这里插入图片描述
  • 操作如下: 修改两处配置

    • dataDir 是存放zookeeper缓存文件,手动创建/usr/zkdata目录 (注意:是目录,别创建成文件)。
    • 配置节点选举节点。server.1=节点主机名称:2888:3888
      参数说明:1,选举id值、2888心跳端口、3888选举端口。
      注:usr/zkdata/ 创建一个文件 myid(注意这个是文件,别创建成目录),编辑内容为选举节点id值。
      在这里插入图片描述

    例如 :server.1 =hd01:2888:3888。 myid内容就是1。
    在这里插入图片描述

3.3.2 给自己机器写编号
  1. 每个克隆节点上都需要,手动创建/usr/zkdata目录,并且在该目录下创建文件myid文件
    • 编写内容是自己的 id号。
      在这里插入图片描述

3.3 启动Zookeeper

  1. 在每个节点上的zookeeper的bin目录里输入启动命令,

    
    	sh zkServer.sh start    启动
    	sh zkServer.sh status   查看节点角色命令
    
    

在这里插入图片描述

4. Zookeeper的工作机制

4.1 zookeeper特性

  1. zookeeper是由一个Leader,和 多个follower组成的集群。
  2. 全局数据一致性。每个server都保存一份相同的数据副本,客户端无论从哪个server上读取,数据都是一致的。
  3. 分布式读取,更新,请求,转发,数据版本都是由Leader协调实施。
  4. 数据原子性。事物机制,即,数据要么都成功,要么失败(不存在成功一般失败一半)。
  5. 实时性。 在一定时间范围内,客户端读到的都是最新数据。

在这里插入图片描述

4.2 Zookeeper的数据结构(重点)

  1. 层次化目录结构,命名规范符合常规命名系统 (如图znode)
  2. 每个节点在zookeeper中叫做 znode, 并且有 唯一标识路径
  3. 节点znode可以包含, 数据,子节点。
    • (官方默认是1M,但是最好,存储数据最好是1k字节以内这样才能保证数据实时性传输)。

znode

4.2.1 节点类型
  1. Znode有 两种类型
    • 短暂(ephemeral) (断开连接自己删除)
    • 持久(persistent) (断开连接不删除)
  2. Znode 有四种形式 的目录节点。
    • PERSISTENT(默认持久)
    • PERSISTENT_SEQUENTIAL(持久序列/test0000000012)
    • EPHEMERAL
    • EPHEMERAL_SEQUENTIAL

5. Zookeeper命令行客户端的使用

5.1 客户端连接

  1. 通过zookeeper自带的命令来连接,在/usr/java/apache-zookeeper-3.6.0-bin/bin 目录下;
    • sh zkCli.sh 连接本机客户端。
      在这里插入图片描述

5.2 Znode 创建节点 临时 顺序 永久

1.创建节点 create [-s] [-e] [-c] [-t ttl] path [data] [acl]

命令节点描述
create [-s] [-e] [-c] [-t ttl] path [data] [acl]默认是持久节点,创建一个新的znode节点,-s表示顺序节点,-e表示临时节点,acl访问控制列表
  1. 分别在zookeeper下创建节点。
    • 创建临时节点 create /zookeeper/zkdata -e/zkephermeral 临时 断开就消失
    • 创建持久节点 create /zookeeper/zkdata/zkpersisten 默认就是持久节点
    • 创建顺序节点 create /zookeeper/zkdata/zkpersistent -s 顺序 后面会自动维护序列号

在这里插入图片描述
在这里插入图片描述

5.3 Znode 查看节点

  1. 查看节点 ls [-s] [-w] [-R] path
命令节点描述
ls [-s] [-w] [-R] path只能查看一级以下的子节点,-s表示获取节点信息,-R表示递归的获取,-w 表示监听
  1. 查看zookeeper下的zkdata信息
    • 查看子节点ls /zookeeper/zkdata
    • 查看子节点信息 ls /zookeeper/zkdata -s
    • 递归查看所有信息 ls -R /zookeeper/zkdata
  2. 查看元数据信息显示内容。
    在这里插入图片描述
    在这里插入图片描述

5.4 Znode 获取节点

  1. 获取节点中的数据 get [-s] [-w] path
命令节点描述
get [-s] [-w] path获取节点数据信息,-s表示获取节点信息,包括时间戳、版本号、数据大小等
  1. 获取zookeeper下zkdata子节点信息
    • 获取节点数据 get /zookeeper/zkdata
    • 获取详细节点数据信息 get /zookeeper/zkdata -s
      在这里插入图片描述

5.5 Znode 更新节点

  1. 更新 节点数据,好比Mysql中的 update 操作 set [-s] path data
命令节点描述
set [-s] path data设置节点数据,-s表示显示当前节点数据

在这里插入图片描述

5.6 Znode 删除节点

  1. 删除节点,有两种方式
    • 删除节点,如果有子节点就无法删除。 delete [-v version] path
    • 递归删除子节点 deleteall path
命令节点描述
delete [-v version] path删除一级节点,只能删除无子节点的节点。
deleteall path递归删除包括子节点

在这里插入图片描述

5.7 Znode 监听

  1. zookeeper对外提供的主要功能之一,监听机制
    • 监听 节点数据变化 (NodeDataChanged) 如:get -w /zookeeper/zkdata
    • 监听 子节点发生改变(NodeChildrenChanged) 如:ls -w /zookeeper/zkdata
    • 监听 删除节点 (NodeDeleted)

在这里插入图片描述
在这里插入图片描述

6. Zookeeper 客户端的Api操作

6.1 增删改查 Znode数据

  1. org.apache.zookeeper.ZooKeeper; 是客户端的主要入口主类,负责建立server的会话它提供了如下的方法:

  2. 创建一个普通的project工程,引入相关jar即可。

    • 如下 增删改查 java API操作。
import org.apache.zookeeper.*;
import org.junit.Before;
import org.junit.Test;

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

public class ZK_Test {
    ZooKeeper zk =null; // 对象提取成员属性。

    //1.创建连接zookeeper,拿到zookeeper的操作对象。
    @Before
    public void init() throws IOException, InterruptedException {
        //1.1 直接创建zk的对象即可。
        /*public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)//连接地址,心跳,检测
            String connectString : 连接服务器的地址
            int sessionTimeout :  客户端超时时间 默认2s 2000
            Watcher watcher :监听事件 interface Watcher是一个接口。
         */
          //开启了递减锁,有阻塞功能
         final CountDownLatch cd = new CountDownLatch(1);
         zk = new ZooKeeper("192.168.150.134:2181,192.168.150.135:2181,192.168.150.136:2181", 2000, new
                Watcher() {
                    @Override
                    public void process(WatchedEvent watchedEvent) { //回调函数
                        // 连接状态是否连接
                        if (watchedEvent.getState()==Event.KeeperState.SyncConnected){
                            System.out.println("连接成功");
                            //连接成功之后就减1
                            cd.countDown();
                        }

                        //打印事件名称。
                        System.out.println("事件名称:"+ watchedEvent.getState());

                        //getData获取数据的回调函数 当watch 设置为true
                       // System.out.println("getDate:"+ watchedEvent.getPath());
                    }
                });
                //如果不成功就阻塞直到成功
                cd.await();
    }

    /*
    创建节点或者子节点
        create(String path, byte[] data, List<ACL> acl, CreateMode createMode)
        path: 创建路径
        data 数据字节类型
        list 权限   ids 谁能查看
        createMode 创建节点类型 顺序还是持久
     */
    @Test //使用单元测试
    public void createNode() throws KeeperException, InterruptedException {
        //拿到zk对象? 需要提取zookeeper zk为成员属性
        zk.create("/zookeeper/zkdata","hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT_SEQUENTIAL);
        zk.close();
    }
    
 /*  
        判断node是否存在
     */
    @Test
    public void  isExist() throws KeeperException, InterruptedException {
         //如果存在 ,则调用回调函数打印  zk.close()
        Stat exists = zk.exists("/zookeeper/zkdata", true); //判断是否存在,
        System.out.println(exists);
        Thread.sleep(Long.MAX_VALUE);
        zk.close();
    }
    
    /*
    查询一个节点
     * byte[] getData(String path, boolean watch, Stat stat)
      path 路径
      watch 监听  false即可. 如果使用true就需要去zkClis.sh 客户端操作set数据
      stat 当前版本信息 赋值null即可。
     */

    @Test
    public void selectNode() throws KeeperException, InterruptedException {
        byte[] data = zk.getData("/zookeeper/zkdata", false, null); //如果要使用true会调用上面监听事件
        //打印字节数组
        System.out.println(new String(data));

        //造成阻塞监听事件  watch 为true情况下。
        //Thread.sleep(Long.MAX_VALUE);
        //关闭
        zk.close();
    }

    /*
        更新数据
         Stat setData(String path, byte[] data, int version)

         path 更新路径
         data 更新数据
         version 版本   -1是所有版本

     */
    @Test
    public void setNode() throws KeeperException, InterruptedException {
        //更新即可。
        // 无需返回值
        zk.setData("/zookeeper/zkdata", "更新数据znode".getBytes(), -1);
        System.out.println("更新成功!");

        zk.close();
    }

    /* 查询子节点数据 
        List<String> getChildren(String path, boolean watch)
        path 路径
        watch 监听 false
     */
    @Test
    public void selectChrildren() throws KeeperException, InterruptedException {
        List<String> children = zk.getChildren("/zookeeper/zkdata", false);
        System.out.println(children); //输出数组[aa,ab,ac]

        zk.close();
    }

    /*删除节点
        delete(String path, int version)
        path 路径
        version 删除版本  -1就是当前版本
     */
     @Test
    public void  deleteNdoe() throws KeeperException, InterruptedException {
        zk.delete("/zookeeper/zkdata/aka",-1);
         System.out.println("删除成功");
        zk.close();
    }
}

6.2 Watch 监听

  1. 当连接客户端之后会调用一次监听的回调函数。
/**
 * Zookeeper监听
 */
public class ZK_Api {

    private  static final  String coon ="192.168.150.134:2181,192.168.150.135:2181,192.168.150.136:2181";
    @Test
    public void watcherNode() throws IOException, KeeperException, InterruptedException {
        final CountDownLatch cd = new CountDownLatch(1);
        ZooKeeper  zk =  new ZooKeeper(coon, 2000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                //1.当zk连接成功之后会调用一次该方法
                System.out.println(watchedEvent.getPath());
                System.out.println(watchedEvent.getType());
                System.out.println(watchedEvent.getState());
                System.out.println("-----------------------------------");

                cd.countDown();
            }
        });

       cd.await();

       // 监听 zkdata数据是否发生改变。  如果此时在Linux客户端下修改数据,就会触发 在回调一次。
       zk.getData("/zookeeper/zkdata",true, null);

        //上面方法执行完就会关闭,休眠一会让它继续等待监听
        Thread.sleep(Long.MAX_VALUE);

       zk.close();

    }


}

在这里插入图片描述

null
None
SyncConnected
-----------------------------------
/zookeeper/zkdata
NodeDataChanged
SyncConnected
-----------------------------------
  1. 循环注册监听。
/**
 * Zookeeper api 循环监听
 */
public class ZK_Api {

    private  static final  String coon ="192.168.150.134:2181,192.168.150.135:2181,192.168.150.136:2181";
    ZooKeeper  zk =null;
    @Test
    public void watcherNode() throws IOException, KeeperException, InterruptedException {
        final CountDownLatch cd = new CountDownLatch(1);
          zk =  new ZooKeeper(coon, 2000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

                try {
                    //只要一直连接在,循环监听。 一直在注册
                    zk.getData("/zookeeper/zkdata",true, null);
                    System.out.println(watchedEvent.getType());
                    System.out.println("---------------");
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                cd.countDown();
            }
        });

       cd.await();

       // 监听 zkdata数据是否发生改变。  如果此时在Linux客户端下修改数据,就会触发 在回调一次。
       //zk.getData("/zookeeper/zkdata",true, null);

        // 该方法只能监听一次。 执行完就释放。使用的也是 zk中的 Watcher监听,
        // 如果想使用自己的回调监听 就new Watcher即可。
        List<String> children = zk.getChildren("/zookeeper/zkdata", true);
        System.out.println("子节点发生变化" + children);
        //上面方法执行完就会关闭,休眠一会让它继续等待监听
        Thread.sleep(Long.MAX_VALUE);

       zk.close();

    }


}

7 服务器上线下线感知

7.1 设计思路分析

  1. 需求,当服务器增减时,客户端能感知到。
  2. 设计思路:
    .csdnimg.cn/e525b9fedb1a42d2bdd69fe4f6a4be07.png)
  3. 需要在Linux环境中,进行打jar包测试,Client.jar 和 Server.jar 。

7.3 服务端开发

  1. 服务端: 注册连接,register节点,模拟业务。
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;

/**
 * zk服务端
 */
public class ZKServer {
    /**
     * 一,获取zookeeper连接
     */
    private static final String conn = "192.168.150.134:2181,192.168.150.135:2181,192.168.150.136:2181";
    ZooKeeper zk = null;
    CountDownLatch cd = new CountDownLatch(1);
    //1.获取zk连接
    public void serverInit() throws IOException, InterruptedException {
        //2.通过构造函数获取连接
        zk  = new ZooKeeper(conn, 2000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

                if (watchedEvent.getState()== Event.KeeperState.SyncConnected){
                    System.out.println("连接成功");
                    cd.countDown();
                }
            }
        });
        cd.await();
    }


    private static final  String pNode = "/server"; //因为每一个节点都要判断父节点是否存在。

    /**
     * 二,向zookeeper注册信息 register
     * @param hostname  注册的机器名
     */
    public void registerServer(String hostname) throws KeeperException, InterruptedException {
        //1.判断所注册的父节点是否存在
        Stat exists = zk.exists(pNode, false);
        // 进行判断是否创建节点
        if (exists==null){
            //2,不存在,就需要创建父节点, 节点持久化
            zk.create(pNode,null, ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
        }

        //2,1创建完父节点之后,在父节点下创建子节点,节点类型? 短暂序号(让zookeeper维护sequential)
        String path = zk.create(pNode + "/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        //打印子节点路径信息
        System.out.println(hostname+" 在线中... "+path);
        
    }


    /**
     * 三,业务功能
     */
    public  void works(String hostname) throws InterruptedException {
        //Todo 要处理的业务
        System.out.println("Server is start...");
        Thread.sleep(Long.MAX_VALUE); //假装在处理。。。
    }

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        ZKServer zkServer = new ZKServer();
        zkServer.serverInit();
        /*
        //模拟上线机器
        System.out.println("请输入要上线的机器名称: hd01 hd02 hd03");
        String pc = new Scanner(System.in).next();
        //注册业务
        zkServer.registerServer(pc);
        //处理业务
        zkServer.works(pc);//
         */

        //注册业务
        zkServer.registerServer(args[0]);
        //处理业务
        zkServer.works(args[0]);//传递参数在linux下
    }
}

  1. 运行环境在Linux下进行测试,需要打jar包。
    • 可以先开启一个Client.jar 作为测试,然后再开启多个Server.jar上线或者下线让客户端进行感

7.2 客户端开发

  1. 客户端 : 注册连接(在回调中更新列表信息), 获取子节点信息,模拟业务。
/**
    客户端业务
 */
public class ZKClient {

    private static final String conn ="192.168.150.134:2181,192.168.150.135:2181,192.168.150.136:2181";
    ZooKeeper zk =null;
    private static final  String pNode = "/server";
    //获取 zk连接
    public void zkinit() throws Exception{
        zk = new ZooKeeper(conn, 2000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                try {
                    //更新类表重新注册监听
                    getNodeLists();
                } catch (Exception e) {}
            }
        });
    }

    /**
     * 一,获取在线pNode/下,子节点信息
     *
     */
    public void getNodeLists() throws KeeperException, InterruptedException {
        //1.获取父类下的子节点
        List<String> children = zk.getChildren(pNode, true); //设置true就会调用上面连接的回调函数。

        for (String chil: children){
            //1.1 获取子节点数据
            byte[] data = zk.getData(pNode + "/" + chil, false, null);

            System.out.println(new String(data)); //输出
            System.out.println("-----------------------");
        }
    }

    /**
     * 三,模拟业务工作
     * @throws Exception
     */
    public void works() throws Exception{
        //处理业务
        System.out.println("Client is start......");
        Thread.sleep(Long.MAX_VALUE);
    }
    //主程序
    public static void main(String[] args) throws Exception {
        ZKClient cilent = new ZKClient();
        //创建连接
        cilent.zkinit();
        //获取子znode 节点信息
        cilent.getNodeLists();
        //启动业务服务
        cilent.works();
    }

}

8 选举机制(简述)

  1. 以一个简单的例子来说明整个选举的过程.

    • 假设,有五台服务器组成的zookeeper集群,它们的 id 从1-5, 同时它们都是,最新启动的, 也就是没有历史数据, 在存放数据量这一点上,都是一样的。假设这些服务器依序启动。
  2. 看看会发生什么?

    • 服务器1启动,此时只有它一台服务器启动了,它发出去的报没有任何响应,所以它的选举状态一直是LOOKING状态。
    • 服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出, 但是由于没有达到超过半数以上的服务器都同意选举它 (这个例子中的半数以上是3), 所以服务器1,2还是继续保持 LOOKING状态.
    • 服务器3启动,根据前面的理论分析,服务器3成为服务器1,2,3中的老大, 而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的leader.
    • 服务器4启动,根据前面的分析,理论上服务器4应该是服务器1,2,3,4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接收当小弟的命了.
    • 服务器5启动,同4一样,当小弟.
  3. 非全新集群的选举机制(数据恢复)

    • 那么,初始化的时候,是按照上述的说明进行选举的,但是当zookeeper运行了一段时间之后,有机器down掉,重新选举时,选举过程就相对复杂了。
    • 需要加入数据version、leader id和逻辑时钟。
      数据version:数据新的version就大,数据每次更新都会更新version。Leader id:就是我们配置的myid中的值,每个机器一个。
  4. 逻辑时钟: 或者叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断。

    • 这个值从О开始递增,每次选举对应一个值,也就是说:如果在同一次选举中,那么这个值应该是一致的;逻辑时钟值越大,说明这一次选举leader的进程更新.选举的标准就变成:
      1、逻辑时钟小的选举结果被忽略,重新投票
      2、统一逻辑时钟后,数据 id大的胜出
      3、数据id相同的情况下,leader id大的胜出
      根据这个规则选出leader。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吴琼老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值