欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Zookeeper知识点总结

程序员文章站 2024-03-23 15:16:22
...

基础

  • Zookeeper = 文件系统 + 通知机制

  • Apach Hbase和 Apache solr 以及 Dubbo等项目都采用了Zookeeper

  • Zookeeper是一个分布式的、高性能的,开源的分布式系统的协调服务,是Google的Chubby一个开源的实现,是Hadoop 和 Hbase 的重要组件,是一个为分布式应用提供数据一致性服务的软件。具有严格顺序访问控制能力的分布式协调存储服务

  • 是一个基于观察者模式设计的分布式服务管理框架,负责存储和管理大家都关心的数据,然后接收观察者Watch的注册,一旦这些数据的状态发生变化,Zookeeper负责通知已经在Zookeeper上注册的那些观察者做出相应的反应,从而实现集群中类似Master/Slave管理模式

  • Zookeeper应用场景:

    • 维护配置信息:作为配置中心,保证配置项配置信息一致性,从而达到维护配置信息的效果
    • 分布式锁服务:多台服务器运行同一种服务,协调个服务的进度。为了保证当某个服务需要进行同步操作时,Zookeeper可以对该操作加锁
    • 集群管理:集群管理各种服务器,当一些服务器加入/移出时会通知给其他正常工作的服务器,以即使调整和分配任务。还对故障服务器诊断并尝试修复
    • 生成分布式唯一ID:分库分表后无法再以靠数据库的auto_increment自动生成唯一ID,此时Zookeeper可以在生成新id时创建持久顺序节点,创建操作返回的节点符号,即为新Id,然后把比自己节点小的删除即可。
  • Zookeeper提供了一套分布式集群管理机制,基于层次性的目录树的数据结构(文件系统),并对树中的结点进行有效管理,从而设计出各种分布式数据管理模型,作为分布式系统的沟通调度桥梁。

  • Zookeeper Service看作是班主任,client看作学生,watch看作是微信,config Data看作是通知信息,一处更新处处更新

Zookeeper知识点总结

能干吗:

  • 命名服务
  • 配置维护
  • 集群管理、
  • 分布式消息同步和协调机制
  • 负载均衡(大多还是用nginx)
  • 对Dubbo的支持

怎么玩

  • 统一命名服务(Name Service 如 Dubbo服务注册中心)
  • 配置管理(Configuration Management,如淘宝考员配置管理框架Diamond)
  • 集群管理(Group Membership, 如Hadoop分布式集群管理)

zookeeper配置文件zoo.cfg

  • ls -ltr :该linux命令工作中很常用,作用是按创建顺序显示当前目录下的文件和目录

  • 5大参数:

    • tickTime=2000:通信心跳数,Zookeeper服务器心跳时间,单位为毫秒。服务器与客户端或服务器之间维持心跳的时间间隔
    • initLimit=10:集群中follower跟随着服务器(F) 与leader领导者服务器(L)之间初始连接时能容忍的最多心跳书(tickTime的数量)。即主机和从机连接所需的时间
    • syncLimit=5:LF同步通信时限,Leader和Follower之间的最大响应时间单位,加入超过 syncLimit * tickTime,Leader认为Follower死掉,从服务器列表删除Follower
    • dataDir=/tmp/zookeeper:数据存放的目录,默认在临时目录temp,一般需要修改到指定目录。
    • clientPort=2182 :默认端口为2182

使用

  • 进入bin:./zkServer.sh start,启动zookeeper服务
  • ./zkCli.sh开启客户端
  • zookeeper服务器端的四字命令(在linux下使用而不是zookeeper下):
    • echo ruok | nc 127.0.0.1 2181 :查看zookeeper的服务器端是否准备就绪
    • echo stat | nc 127.0.0.1 2181 :查看zookeeper服务器端状态
    • echo envi | nc 127.0.0.1 2181 :查看zookeeper服务器端环境
  • create /testNode v1:创建节点 get /testNode :获取节点的data。
  • set /testNode v2:覆盖 testNode的value
  • get /testNode :查询该节点的数据
  • ls/ls2 /testNode :查询该节点信息,ls只显示子节点,ls2显示所有信息
  • 删除节点:delete/rmr /testNode

zookeeper数据模型

  • Zookeeper所使用的数据模型风格很像文件系统的目录树结构。有点类似windows中注册表的结构
  • 有名称、有树节点,有键值对的关系
  • 可以看作是一个树形结构的数据库,但又不能存放数据,作用是对分布在不同机器上做名称管理
  • Stat结构体:
    • Zookeeper知识点总结

数据模型znode节点

  • Zookeeper数据模型结构与Unix文件系统很类似,整体看作一棵树,自身维护一套存储数据结构,每个节点是一个ZNode

  • 每一个znode默认能存储1MB数据,每个ZNode都可以通过其路径唯一标识

  • create /node1/node2 val :报错,不支持多级创建节点

  • Znode = path + nodeValue + Stat 。即 set /path nodeValue,自带Stat,存放该节点的一些信息

  • znode中的存在类型:持久,临时。但细分要有四种

  • create -s /myNode v2 : -s 表示节点自动从myNode后面添加***,确保该节点不重复

  • create -e /myNode v2 : -e 代表临时节点,重启将失效,默认-p(可不写)表示持久化。-s和-e可同时用。

  • 一个节点对应一个应用,节点存储的数据就是应用所需要的配置信息

通话机制:Session + Watch

  • 客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变,被删除、子目录节点增加删除)时,zookeeper会通知客户端。

  • Session :通过Java建立Zookeeper会话,此时处于CONNECTING状态,当客户端连接Zookeeper服务成功后进入CONNECTED状态 ,结束状态CLOSED

  • Watch(观察者)

    • 异步+回调+的触发机制
    • 异步和回调的理解:假如面试我一结束,面试官口渴托我买瓶可乐,如果在我去买可乐过程中面试官一直在等而不继续面试,这叫同步;若在我买水的过程中面试继续进行,这是异步;若买水时发现没有可乐,这时打电话询问面试官说明情况的这个过程,就叫异步回调。
    • 客户端 可以在每个znode节点上设置一个Watcher,如果被观察的服务端的znode节点有变更,watch会触发,这和watch所属的客户端将接收到一个通知包被告知节点已经变化,把响应的事件通知给设置Watcher的Client端
    • zookeeper里所有读取操作:getData() , getCildren() 和 exists() 都有设置watch的选项
  • watch事件理解

    • 1.一次性触发:只监控一次,触发一次后不再有效。一般不多用
    • 2.发送客户端
    • 3.为数据设置watch
    • 4.时序性和一致性

Java操作Zookeeper

Maven工程和配置POM

  • 依赖

            <!-- 注册中心使用的是zookeeper,引入操作zookeeper的客户端 -->
    		<dependency>
    			<groupId>org.apache.curator</groupId>
    			<artifactId>curator-framework</artifactId>
    			<version>2.12.0</version>
    		</dependency>
            <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
                <version>RELEASE</version>
            </dependency>
    
  • WatchOne 一次性触发watch,数据监控

public class WatchOne {
    private static final Logger logger = LoggerFactory.getLogger(WatchOne.class);
    private final static String CONNECTSTRING = "192.168.137.133:2181";
    private final static int SESSION_TIMEOUT = 50 * 1000;
    private final static String PATH = "/atguigu";
    private ZooKeeper zk = null;

    public ZooKeeper startZK() throws Exception {
        return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
            }
        });
    }

    public void stopZK() throws Exception {
        if(zk != null)  zk.close();
    }

    public void createZnode(String nodePath, String nodeValue) throws Exception {
        // OPEN_ACL_UNSAFE:类似关闭防火墙
        // CreateMode.PERSISTENT: 创建的节点由Java控制是持久的
        zk.create(nodePath, nodeValue.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    public String getZnode(String nodePath) throws Exception {
        String result = null;
        byte[] byteArray = zk.getData(PATH, new Watcher() { // 一次性触发
            @Override
            public void process(WatchedEvent event) {
                try {
                    trigerValue(PATH);
                }  catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, new Stat());
        result = new String(byteArray); // 转为字符串
        return result;
    }

    public String trigerValue(String nodePath) throws Exception {
        String result = null;
        byte[] byteArray = zk.getData(PATH, false, new Stat());
        result = new String(byteArray); // 转为字符串
        return result;
    }

    public static void main(String[] args) throws Exception {
        WatchOne watchOne = new WatchOne();
        watchOne.setZk(watchOne.startZK());
        if(watchOne.getZk().exists(PATH, false) == null){ // 没有该节点才能创建,不然会报错,节点是不能覆盖的
            watchOne.createZnode( PATH, "AAA");
            String retValue = watchOne.getZnode(PATH);
            logger.info("retValue="+retValue);
            Thread.sleep(Long.MAX_VALUE);
        }else{
            logger.info("I have no znode");
        }
    }
    public ZooKeeper getZk() {
        return zk;
    }
    public void setZk(ZooKeeper zk) {
        this.zk = zk;
    }
}

WatchMore 长久数据监控(常用)

public class WatchMore {
    private static final Logger logger = LoggerFactory.getLogger(WatchMore.class);
    private final static String CONNECTSTRING = "192.168.137.133:2181";
    private final static int SESSION_TIMEOUT = 50 * 1000;
    private final static String PATH = "/atguigu";
    private ZooKeeper zk = null;
    private String oldValue = null;

    public ZooKeeper startZK() throws Exception {
        return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
            }
        });
    }

    public void stopZK() throws Exception {
        if(zk != null)  zk.close();
    }

    public void createZnode(String nodePath, String nodeValue) throws Exception {
        // OPEN_ACL_UNSAFE:类似关闭防火墙
        // CreateMode.PERSISTENT: 创建的节点由Java控制是持久的
        zk.create(nodePath, nodeValue.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    public String getZnode(String nodePath) throws Exception {
        String result = null;
        byte[] byteArray = zk.getData(PATH, new Watcher() { // 一次性触发
            @Override
            public void process(WatchedEvent event) {
                try {
                    trigerValue(PATH);
                }  catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, new Stat());
        result = new String(byteArray); // 转为字符串
        oldValue = result; // 一开始初始值
        return result;
    }

	// 每次所观察的节点改变就触发 
    public boolean trigerValue(String nodePath) throws Exception {
        String result = null;
        byte[] byteArray = zk.getData(PATH, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try {
                    trigerValue(nodePath);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, new Stat());
        result = new String(byteArray); // 转为字符串
        String newValue = result;
        if(oldValue.equals(newValue)){
            logger.info("*********no changes");
            return false;
        }else{
            logger.info("************oldValue:"+oldValue+"\t newValue:"+newValue);
            oldValue = newValue;
            return true;
        }
    }
	// 监控节点 /atguigu下的数据
    public static void main(String[] args) throws Exception {
        WatchMore watchOne = new WatchMore();
        watchOne.setZk(watchOne.startZK());
        if(watchOne.getZk().exists(PATH, false) == null){ // 没有该节点才能创建,不然会报错,节点是不能覆盖的
            watchOne.createZnode( PATH, "AAA");
            String retValue = watchOne.getZnode(PATH);
            logger.info("retValue="+retValue);
            Thread.sleep(Long.MAX_VALUE);
        }else{
            logger.info("I have no znode");
        }
    }
    public ZooKeeper getZk() {
        return zk;
    }
    public void setZk(ZooKeeper zk) {
        this.zk = zk;
    }

    public String getOldValue() {
        return oldValue;
    }

    public void setOldValue(String oldValue) {
        this.oldValue = oldValue;
    }
}

子节点变化监控(不常用)

package com.xuecheng.manage_course;

import org.apache.zookeeper.*;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;


public class WatchChild {
    private static final Logger logger = LoggerFactory.getLogger(WatchChild.class);

    private final static String CONNECTSTRING = "192.168.137.133:2181";
    private final static int SESSION_TIMEOUT = 50 * 1000;
    private final static String PATH = "/atguigu";
    private ZooKeeper zk = null;

    public ZooKeeper startZK() throws Exception {
        return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() { // 开启监控
            @Override
            public void process(WatchedEvent event) {
                // 子节点没改变
                if(event.getType() == EventType.NodeChildrenChanged && event.getPath().equals(PATH)){
                    showChildNode(PATH); // 子节点每次改变都会执行该方法
                }else{
                    showChildNode(PATH); // 一开始会注册父亲节点并打印初始子节点
                }
            }
        });
    }

    public void showChildNode(String nodePath) {
        List<String> list = null;
        try {
            // 获取该节点所有子节点
            list = zk.getChildren(PATH, true); // true表示该节点下全部监控,自带连续监控
            logger.info("***********"+list);
        } catch (KeeperException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 监控子节点
    public static void main(String[] args) throws Exception {
        WatchChild watchChild = new WatchChild();
        watchChild.setZk(watchChild.startZK());
        Thread.sleep(Long.MAX_VALUE);

    }
    public ZooKeeper getZk() {
        return zk;
    }
    public void setZk(ZooKeeper zk) {
        this.zk = zk;
    }
}

Zookeeper集群

  • 分为服务器端和客户端,客户端之连接到某个服务器上,客户端使用并维护一个TCP连接,第一次连接会建立会话,当这个客户端连接到另外服务器时,这个会话会被新的服务器重新建立

  • 配置项书写格式:server.N = YYY:A:B,其中,N为服务器编号,YYY表示服务器IP地址,A为LF(主从机)通信端口,即该服务器与集群中的leader交换的信息的端口。B为选举端口,即选举新leader时服务器间相互通信的端口(当leader挂掉后,其余服务器会相互通信并选举出新的leader)

  • 真集群中每个服务器A端口和B端口都是一样的。

  • 伪集群中每个服务器A端口和B端口都是不一样的。但IP一样

  • 伪集群做法:

    • 分别复制zookeeper目录为zk01、zk02、zk03并分别在配置文件修改以下:

Zookeeper知识点总结

Zookeeper知识点总结

​ 此时使用zk01和zk02作为服务器,zk03连接server作为客户端:./zkCli.sh -server 127.0.0.1:2193

​ 此时使用zk03改变节点数据,zk01和zk02便可查到刚改变的数据,完成伪集群