【图解版】ZooKeeper初探

参考博客

Zookeeper简单介绍
Zookeeper安装部署及hello world
zookeeper
zookeeper原理(转)
netstat -ano 查看机器端口占用情况
Zookeeper的安装和配置(单机模式)
zookeeper节点Watch机制实例展示
Zookeeper可以干什么
Zookeeper工作原理(详细)
ZooKeeper监听机制
浅析Zookeeper的一致性原理
ZooKeeper 节点类型
ZooKeeper系列3:ZooKeeper命令、命令行工具及简单操作

整体思路

什么是zookeeper?
zookeeper环境搭建和安装启动?
zookeeper常用命令?
zookeeper原理分析?
zookeeper有什么作用?

什么是zookeeper

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:***配置维护、域名服务、分布式同步、组服务***等

了解zookeeper,需要知道哪些知识

分布式协调技术
分布式锁
数据模型Znode
Watcher机制
共享锁
Zab协议
Zxid
单点故障
observer
节点

分布式协调技术: 主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种临界资源,防止造成"脏数据"的后果
这里写图片描述
分布式锁:假设在第一台机器上挂载了一个资源,然后这三个物理分布的进程都要竞争这个资源,但我们又不希望他们同时进行访问,这时候我们就需要一个协调器,来让他们有序的来访问这个资源。这个协调器就是我们经常提到的那个锁,比如说"进程-1"在使用该资源的时候,会先去获得锁,"进程1"获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,"进程1"用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。

zookeeper性能特点:可靠性方面来说,它并不会因为一个节点的错误而崩溃。除此之外,它严格的序列访问控制意味着复杂的控制原语可以应用在客户端上。ZooKeeper在一致性、可用性、容错性的保证。它虽然不能避免网络故障,但它能够保证每时每刻只有一个Master。

Zonde:Zonde通过路径引用,如同Unix中的文件路径。路径必须是绝对的,因此他们必须由斜杠字符来开头。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在ZooKeeper中,路径由Unicode字符串组成,并且有一些限制。字符串"/zookeeper"用以保存管理信息,比如关键配额信息。每个Znode由3部分组成: ① stat:此为状态信息, 描述该Znode的版本, 权限等信息 ② data:与该Znode关联的数据 ③ children:该Znode下的子节点。**数据节点(dataNode):zk数据模型中的最小数据单元,数据模型是一棵树,由斜杠(/)分割的路径名唯一标识,数据节点可以存储数据内容及一系列属性信息,同时还可以挂载子节点,构成一个层次化的命名空间。**ZooKeeper 实现的任何功能都离不开ZooKeeper的数据结构,任何功能的实现都是利用"Znode结构特性+节点关联的数据"来实现的.
这里写图片描述

Zonde结构特点:
① 每个子目录项如 NameService 都被称作为 znode,这个 znode 是被它所在的路径唯一标识,如 Server1 这个 znode 的标识为 /NameService/Server1;
② znode 可以有子节点目录,并且每个 znode 可以存储数据,注意 EPHEMERAL 类型的目录节点不能有子节点目录;
③ znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据;
④ znode 可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除,Zookeeper 的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了;
⑤ znode 的目录名可以自动编号,如 App1 已经存在,再创建的话,将会自动命名为 App2;
⑥ znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是 Zookeeper 的核心特性,Zookeeper 的很多功能都是基于这个特性实现的。

双Master故障:在回复的时候网络发生故障,这样我们的备用节点同样收不到回复,就会认为主节点挂了,然后备用节点将他的Master实例启动起来,这样我们的分布式系统当中就有了两个主节点也就是—双Master, 出现Master以后我们的从节点就会将它所做的事一部分汇报给了主节点,一部分汇报给了从节点,实际上出现2个Master汇报服务时候根本不知道交给谁处理,这样服务就全乱了。为了防止出现这种情况,我们引入了 ZooKeeper,它虽然不能避免网络故障,但它能够保证每时每刻只有一个Master。

单点故障:通常分布式系统采用主从模式,就是一个主控机连接多个处理节点。主节点负责分发任务,从节点负责处理任务,当我们的主节点发生故障时,那么整个系统就都瘫痪了,那么我们把这种故障叫作单点故障。

Watcher机制:一个Watch事件是一个一次性的触发器,当被设置了Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端,以便通知它们。客户端可以在节点上设置watch,我们称之为监视器。当节点状态发生改变时(Znode的增、删、改)将会触发watch所对应的操作。当watch被触发时,ZooKeeper将会向客户端发送且仅发送一条通知,因为watch只能被触发一次,这样可以减少网络流量。

Zxid(时间):ZooKeeper节点状态改变的每一个操作都将使节点接收到一个Zxid格式的时间戳,并且这个时间戳全局有序。也就是说,也就是说,每个对 节点的改变都将产生一个唯一的Zxid。如果Zxid1的值小于Zxid2的值,那么Zxid1所对应的事件发生在Zxid2所对应的事件之前。实际 上,ZooKeeper的每个节点维护者三个Zxid值,为别为:cZxid、mZxid、pZxid。

数据快照:数据快照是zk数据存储中另一个非常核心的运行机制。数据快照用来记录zk服务器上某一时刻的全量内存数据内容,并将其写入到指定的磁盘文件中,可通过dataDir配置文件目录。可配置参数snapCount,设置两次快照之间的事务操作个数,zk节点记录完事务日志时,会统计判断是否需要做数据快照(距离上次快照,事务操作次数等于snapCount/2~snapCount 中的某个值时,会触发快照生成操作,随机值是为了避免所有节点同时生成快照,导致集群影响缓慢)。

选举:在引入了Zookeeper以后我们启动了两个主节点,“主节点-A"和"主节点-B"他们启动以后,都向ZooKeeper去注册一个节点。我们 假设"主节点-A"锁注册地节点是"master-00001”,“主节点-B"注册的节点是"master-00002”,注册完以后进行选举,编号最 小的节点将在选举中获胜获得锁成为主节点,也就是我们的"主节点-A"将会获得锁成为主节点,然后"主节点-B"将被阻塞成为一个备用节点。那么,通过这 种方式就完成了对两个Master进程的调度。
这里写图片描述

节点类型:
持久节点(PERSISTENT)
所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。
持久顺序节点(PERSISTENT_SEQUENTIAL)
这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。
临时节点(EPHEMERAL)
和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。
临时顺序节点(EPHEMERAL_SEQUENTIAL)
可以用来实现分布式锁
客户端调用create()方法创建名为“locknode/guid-lock-”的节点,需要注意的是,这里节点的创建类型需要设置为EPHEMERAL_SEQUENTIAL。
客户端调用getChildren(“locknode”)方法来获取所有已经创建的子节点,注意,这里不注册任何Watcher。
客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点序号最小,那么就认为这个客户端获得了锁。
如果在步骤3中发现自己并非所有子节点中最小的,说明自己还没有获取到锁。此时客户端需要找到比自己小的那个节点,然后对其调用exist()方法,同时注册事件监听。
之后当这个被关注的节点被移除了,客户端会收到相应的通知。这个时候客户端需要再次调用getChildren(“locknode”)方法来获取所有已经创建的子节点,确保自己确实是最小的节点了,然后进入步骤3。

安装配置

Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。

■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境;
■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例;
■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble)

单机模式(Windows):

  • 下载地址
  • 备份zoo_sample.cfg文件,修改为zoo.cfg,修改其中的参数
    dataDir=C:\Users\Administrator\Downloads\zookeeper-3.4.8\tmp\zookeeper\data(自己的路径)
    dataLogDir=\Users\Administrator\Downloads\zookeeper-3.4.8\tmp\zookeeper\data\dataLog (自己的路径)
  • 启动关闭:zkServer.cmd zkServer
  • 启动完成后使用zkCli.cmd-server 127.0.0.1:2181查看运行情况这里写图片描述出现welcome to Zookeeper证明启动成功,也可以直接使用netstat -ano|findstr 2181根据端口来查看

伪集群模式(Windows):

  • 复制zoo.cfg为三份zoo1.cfg zoo2.cfg zoo3.cfg 复制zkServer.cmd 为三份zkServer-1.cmd zkServer-2.cmd zkServer-3.cmd
  • 修改zoo1.cfg 内容
    dataDir=/tmp/zookeeper/1
    server.1=localhost:2887:3887
    server.1=localhost:2888:3888
    server.1=localhost:2889:3889这里写图片描述
    依次类推
  • 修改zkServer-1.cmd内容
    加入set ZOOCFG=…\conf\zoo1.cfg这里写图片描述
    依次类推
  • 创建myid文件,不要后缀
    /tmp/zookeeper/1,
    /tmp/zookeeper/2,
    /tmp/zookeeper/3
    分别启动这3个cmd文件,在控制台输入jps会看到启动的三个Java进程,说明启动成功
    这里写图片描述

代码连接

package com.test.zookeeper;

import java.io.IOException;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

public class Test {
	  public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
	        ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 300000, new DemoWatcher());//连接zk server
	        String node = "/app1";
	        Stat stat = zk.exists(node, false);//检测/app1是否存在
	        if (stat == null) {
	            //创建节点
	            String createResult = zk.create(node, "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
	            System.out.println(createResult);
	        }
	        //获取节点的值
	        byte[] b = zk.getData(node, false, stat);
	        System.out.println(new String(b));
	        zk.close();
	    }
	 
	    static class DemoWatcher implements Watcher {
	        @Override
	        public void process(WatchedEvent event) {
	            System.out.println("----------->");
	            System.out.println("path:" + event.getPath());
	            System.out.println("type:" + event.getType());
	            System.out.println("stat:" + event.getState());
	            System.out.println("<-----------");
	        }
	    }
}

我们使用zkCli.cmd -server localhost:2181连接获取app1下的节点数据,产生数据test
这里写图片描述

常用命令
查看节点下的内容:
ls /;
创建一个新的 znode:
create /zk myData;
get 命令来确认第二步中所创建的 znode 是否包含我们所创建的字符串:
get /zk;
set 命令来对 zk 所关联的字符串进行设置:
set /zk shenlan211314;
将刚才创建的 znode 删除:
delete /zk

集群下的测试类

package com.test.zookeeper;

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

import org.apache.log4j.Logger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.LoggerFactory;

public class ZkWatchAPI implements Watcher {

    public static final org.slf4j.Logger LOG =  LoggerFactory.getLogger(ZkWatchAPI.class);

    private static final int SESSION_TIMEOUT = 10000;

    private ZooKeeper zk = null;

    private CountDownLatch connectedSemaphore = new CountDownLatch( 1 );

    /**
     * 连接Zookeeper
     * @param connectString  Zookeeper服务地址
     */
    public void connectionZookeeper(String connectString){
        connectionZookeeper(connectString,SESSION_TIMEOUT);
    }

    /**
     * <p>连接Zookeeper</p>
     * <pre>
     *     [关于connectString服务器地址配置]
     *     格式: 192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181
     *     这个地址配置有多个ip:port之间逗号分隔,底层操作
     *     ConnectStringParser connectStringParser =  new ConnectStringParser(“192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181”);
     *     这个类主要就是解析传入地址列表字符串,将其它保存在一个ArrayList中
     *     ArrayList<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>();
     *     接下去,这个地址列表会被进一步封装成StaticHostProvider对象,并且在运行过程中,一直是这个对象来维护整个地址列表。
     *     ZK客户端将所有Server保存在一个List中,然后随机打乱(这个随机过程是一次性的),并且形成一个环,具体使用的时候,从0号位开始一个一个使用。
     *     因此,Server地址能够重复配置,这样能够弥补客户端无法设置Server权重的缺陷,但是也会加大风险。
     *
     *     [客户端和服务端会话说明]
     *     ZooKeeper中,客户端和服务端建立连接后,会话随之建立,生成一个全局唯一的会话ID(Session ID)。
     *     服务器和客户端之间维持的是一个长连接,在SESSION_TIMEOUT时间内,服务器会确定客户端是否正常连接(客户端会定时向服务器发送heart_beat,服务器重置下次SESSION_TIMEOUT时间)。
     *     因此,在正常情况下,Session一直有效,并且ZK集群所有机器上都保存这个Session信息。
     *     在出现网络或其它问题情况下(例如客户端所连接的那台ZK机器挂了,或是其它原因的网络闪断),客户端与当前连接的那台服务器之间连接断了,
     *     这个时候客户端会主动在地址列表(实例化ZK对象的时候传入构造方法的那个参数connectString)中选择新的地址进行连接。
     *
     *     [会话时间]
     *     客户端并不是可以随意设置这个会话超时时间,在ZK服务器端对会话超时时间是有限制的,主要是minSessionTimeout和maxSessionTimeout这两个参数设置的。
     *     如果客户端设置的超时时间不在这个范围,那么会被强制设置为最大或最小时间。 默认的Session超时时间是在2 * tickTime ~ 20 * tickTime
     * </pre>
     * @param connectString  Zookeeper服务地址
     * @param sessionTimeout Zookeeper连接超时时间
     */
    public void connectionZookeeper(String connectString, int sessionTimeout){
        this.releaseConnection();
        try {
            // ZK客户端允许我们将ZK服务器的所有地址都配置在这里
            zk = new ZooKeeper(connectString, sessionTimeout, this );
            // 使用CountDownLatch.await()的线程(当前线程)阻塞直到所有其它拥有CountDownLatch的线程执行完毕(countDown()结果为0)
            connectedSemaphore.await();
        } catch ( InterruptedException e ) {
            LOG.error("连接创建失败,发生 InterruptedException , e " + e.getMessage(), e);
        } catch ( IOException e ) {
            LOG.error( "连接创建失败,发生 IOException , e " + e.getMessage(), e );
        }
    }

    /**
     * <p>创建zNode节点, String create(path<节点路径>, data[]<节点内容>, List(ACL访问控制列表), CreateMode<zNode创建类型>) </p><br/>
     * <pre>
     *     节点创建类型(CreateMode)
     *     1、PERSISTENT:持久化节点
     *     2、PERSISTENT_SEQUENTIAL:顺序自动编号持久化节点,这种节点会根据当前已存在的节点数自动加 1
     *     3、EPHEMERAL:临时节点客户端,session超时这类节点就会被自动删除
     *     4、EPHEMERAL_SEQUENTIAL:临时自动编号节点
     * </pre>
     * @param path zNode节点路径
     * @param data zNode数据内容
     * @return 创建成功返回true, 反之返回false.
     */
    public boolean createPath( String path, String data ) {
        try {
            String zkPath =  this.zk.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            LOG.info( "节点创建成功, Path: " + zkPath + ", content: " + data );
            return true;
        } catch ( KeeperException e ) {
            LOG.error( "节点创建失败, 发生KeeperException! path: " + path + ", data:" + data
                    + ", errMsg:" + e.getMessage(), e );
        } catch ( InterruptedException e ) {
            LOG.error( "节点创建失败, 发生 InterruptedException! path: " + path + ", data:" + data
                    + ", errMsg:" + e.getMessage(), e );
        }
        return false;
    }

    /**
     * <p>删除一个zMode节点, void delete(path<节点路径>, stat<数据版本号>)</p><br/>
     * <pre>
     *     说明
     *     1、版本号不一致,无法进行数据删除操作.
     *     2、如果版本号与znode的版本号不一致,将无法删除,是一种乐观加锁机制;如果将版本号设置为-1,不会去检测版本,直接删除.
     * </pre>
     * @param path zNode节点路径
     * @return 删除成功返回true,反之返回false.
     */
    public boolean deletePath( String path ){
        try {
            this.zk.delete(path,-1);
            LOG.info( "节点删除成功, Path: " + path);
            return true;
        } catch ( KeeperException e ) {
            LOG.error( "节点删除失败, 发生KeeperException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        } catch ( InterruptedException e ) {
            LOG.error( "节点删除失败, 发生 InterruptedException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        }
        return false;
    }

    /**
     * <p>更新指定节点数据内容, Stat setData(path<节点路径>, data[]<节点内容>, stat<数据版本号>)</p>
     * <pre>
     *     设置某个znode上的数据时如果为-1,跳过版本检查
     * </pre>
     * @param path zNode节点路径
     * @param data zNode数据内容
     * @return 更新成功返回true,返回返回false
     */
    public boolean writeData( String path, String data){
        try {
            Stat stat = this.zk.setData(path, data.getBytes(), -1);
            LOG.info( "更新数据成功, path:" + path + ", stat: " + stat );
            return true;
        } catch (KeeperException e) {
            LOG.error( "更新数据失败, 发生KeeperException! path: " + path + ", data:" + data
                    + ", errMsg:" + e.getMessage(), e );
        } catch (InterruptedException e) {
            LOG.error( "更新数据失败, 发生InterruptedException! path: " + path + ", data:" + data
                    + ", errMsg:" + e.getMessage(), e );
        }
        return false;
    }

    /**
     * <p>读取指定节点数据内容,byte[] getData(path<节点路径>, watcher<监视器>, stat<数据版本号>)</p>
     * @param path zNode节点路径
     * @return 节点存储的值,有值返回,无值返回null
     */
    public String readData( String path ){
        String data = null;
        try {
            data = new String( this.zk.getData( path, false, null ) );
            LOG.info( "读取数据成功, path:" + path + ", content:" + data);
        } catch (KeeperException e) {
            LOG.error( "读取数据失败,发生KeeperException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        } catch (InterruptedException e) {
            LOG.error( "读取数据失败,发生InterruptedException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        }
        return  data;
    }

    /**
     * <p>获取某个节点下的所有子节点,List getChildren(path<节点路径>, watcher<监视器>)该方法有多个重载</p>
     * @param path zNode节点路径
     * @return 子节点路径集合 说明,这里返回的值为节点名
     * <pre>
     *     eg.
     *     /node
     *     /node/child1
     *     /node/child2
     *     getChild( "node" )户的集合中的值为["child1","child2"]
     * </pre>
     *
     *
     *
     * @throws KeeperException
     * @throws InterruptedException
     */
    public List<String> getChild( String path ){
        try{
            List<String> list=this.zk.getChildren( path, false );
            if(list.isEmpty()){
                LOG.info( "中没有节点" + path );
            }
            return list;
        }catch (KeeperException e) {
            LOG.error( "读取子节点数据失败,发生KeeperException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        } catch (InterruptedException e) {
            LOG.error( "读取子节点数据失败,发生InterruptedException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        }
        return null;
    }

    /**
     * <p>判断某个zNode节点是否存在, Stat exists(path<节点路径>, watch<并设置是否监控这个目录节点,这里的 watcher 是在创建 ZooKeeper 实例时指定的 watcher>)</p>
     * @param path zNode节点路径
     * @return 存在返回true,反之返回false
     */
    public boolean isExists( String path ){
        try {
            Stat stat = this.zk.exists( path, false );
            return null != stat;
        } catch (KeeperException e) {
            LOG.error( "读取数据失败,发生KeeperException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        } catch (InterruptedException e) {
            LOG.error( "读取数据失败,发生InterruptedException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        }
        return false;
    }

    /**
     * Watcher Server,处理收到的变更
     * @param watchedEvent
     */
    @Override
    public void process(WatchedEvent watchedEvent) {
        LOG.info("收到事件通知:" + watchedEvent.getState() );
        if ( Event.KeeperState.SyncConnected == watchedEvent.getState() ) {
            connectedSemaphore.countDown();
        }
    }

    /**
     * 关闭ZK连接
     */
    public void releaseConnection() {
        if ( null != zk ) {
            try {
                this.zk.close();
            } catch ( InterruptedException e ) {
                LOG.error("release connection error ," + e.getMessage() ,e);
            }
        }
    }

    public static void main(String [] args){

        // 定义父子类节点路径
        String rootPath = "/nodeRoot";
        String child1Path = rootPath + "/nodeChildren1";
        String child2Path = rootPath + "/nodeChildren2";

        ZkWatchAPI zkWatchAPI = new ZkWatchAPI();

        // 连接zk服务器
//        zkWatchAPI.connectionZookeeper("192.168.43.110:2181"); 
        zkWatchAPI.connectionZookeeper("192.168.43.110:2181,192.168.43.110:2182,192.168.43.110:2183");

        // 创建节点数据
        if ( zkWatchAPI.createPath( rootPath, "<父>节点数据" ) ) {
            System.out.println( "节点[" + rootPath + "]数据内容[" + zkWatchAPI.readData( rootPath ) + "]" );
        }
        // 创建子节点, 读取 + 删除
        if ( zkWatchAPI.createPath( child1Path, "<父-子(1)>节点数据" ) ) {
            System.out.println( "节点[" + child1Path + "]数据内容[" + zkWatchAPI.readData( child1Path ) + "]" );
            zkWatchAPI.deletePath(child1Path);
            System.out.println( "节点[" + child1Path + "]删除值后[" + zkWatchAPI.readData( child1Path ) + "]" );
        }

        // 创建子节点, 读取 + 修改
        if ( zkWatchAPI.createPath( child2Path, "<父-子(2)>节点数据" ) ) {
            System.out.println( "节点[" + child2Path + "]数据内容[" + zkWatchAPI.readData( child2Path ) + "]" );
            zkWatchAPI.writeData( child2Path, "<父-子(2)>节点数据,更新后的数据" );
            System.out.println( "节点[" + child2Path+ "]数据内容更新后[" + zkWatchAPI.readData( child2Path ) + "]" );
        }

        // 获取子节点
        List<String> childPaths = zkWatchAPI.getChild(rootPath);
        if(null != childPaths){
            System.out.println( "节点[" + rootPath + "]下的子节点数[" + childPaths.size() + "]" );
            for(String childPath : childPaths){
                System.out.println(" |--节点名[" +  childPath +  "]");
            }
        }

        // 判断节点是否存在
        System.out.println( "检测节点[" + rootPath + "]是否存在:" + zkWatchAPI.isExists(rootPath)  );
        System.out.println( "检测节点[" + child1Path + "]是否存在:" + zkWatchAPI.isExists(child1Path)  );
        System.out.println( "检测节点[" + child2Path + "]是否存在:" + zkWatchAPI.isExists(child2Path)  );


        zkWatchAPI.releaseConnection();
    }

}

zookeeper的具体作用

- 配置服务
在我们的应用中除了代码外,还有一些就是各种配置。比如数据库连接等。一般我们都是使用配置文件的方式,在代码中引入这些配置文件。但是当我们只有一种配置,只有一台服务器,并且不经常修改的时候,使用配置文件是一个很好的做法,但是如果我们配置非常多,有很多服务器都需要这个配置,而且还可能是动态的话使用配置文件就不是个好主意了。这个时候往往需要寻找一种集中管理配置的方法,我们在这个集中的地方修改了配置,所有对这个配置感兴趣的都可以获得变更。比如我们可以把配置放在数据库里,然后所有需要配置的服务都去这个数据库读取配置。但是,因为很多服务的正常运行都非常依赖这个配置,所以需要这个集中提供配置服务的服务具备很高的可靠性。一般我们可以用一个集群来提供这个配置服务,但是用集群提升可靠性,那如何保证配置在集群中的一致性呢? 这个时候就需要使用一种实现了一致性协议的服务了。Zookeeper就是这种服务,它使用Zab这种一致性协议来提供一致性。现在有很多开源项目使用Zookeeper来维护配置,比如在HBase中,客户端就是连接一个Zookeeper,获得必要的HBase集群的配置信息,然后才可以进一步操作。还有在开源的消息队列Kafka中,也使用Zookeeper来维护broker的信息。在Alibaba开源的SOA框架Dubbo中也广泛的使用Zookeeper管理一些配置来实现服务治理。
这里写图片描述
Zookeeper很容易实现这种集中式的配置管理,比如将所需要的配置信息放到/Configuration 节点上,集群中所有机器一启动就会通过Client对/Configuration这个节点进行监控【zk.exist("/Configuration″,true)】,并且实现Watcher回调方法process(),那么在zookeeper上/Configuration节点下数据发生变化的时候,每个机器都会收到通知,Watcher回调方法将会被执行,那么应用再取下数据即可【zk.getData("/Configuration″,false,null)】。

- 统一命名服务(Name Service)
分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,通常情况下用树形的名称结构是一个理想的选择,树形的名称 结构是一个有层次的目录结构,既对人友好又不会重复。说到这里你可能想到了 JNDI,没错 Zookeeper 的 Name Service 与 JNDI 能够完成的功能是差不多的,它们都是将有层次的目录结构关联到一定资源上,但是Zookeeper的Name Service 更加是广泛意义上的关联,也许你并不需要将名称关联到特定资源上,你可能只需要一个不会重复名称,就像数据库中产生一个唯一的数字主键一样。
阿里开源的分布式服务框架Dubbo中使用ZooKeeper来作为其命名服务,维护全局的服务地址列表。在Dubbo实现中: 服务提供者在启动的时候,向ZK上的指定节点/dubbo/{serviceName}/providers目录下写入自己的URL地址,这个操作就完成了服务的发布。 服务消费者启 动的时候,订阅/dubbo/{serviceName}/providers目录下的提供者URL地址, 并向/dubbo/{serviceName} /consumers目录下写入自己的URL地址。 注意,所有向ZK上注册的地址都是临时节点,这样就能够保证服务提供者和消费者能够自动感应资源的变化。 另外,Dubbo还有针对服务粒度的监控,方法是订阅/dubbo/{serviceName}目录下所有提供者和消费者的信息。

- 分布式锁
分布式锁,这个主要得益于ZooKeeper为我们保证了数据的强一致性,即用户只要完全相信每时每刻,zk集群中任意节点(一个zk server)上的相同znode的数据是一定是相同的。锁服务可以分为两类,一个是保持独占,另一个是控制时序。

保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把 锁。通常的做法是把ZK上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。

控制时序,就是所有试图来获取这个锁的客户端,最终都是会被安排执行,只是有 个全局时序了。做法和上面基本类似,只是这里 /distribute_lock 已经预先存在,客户端在它下面创建临时有序节点。Zk的父节点(/distribute_lock)维持一份sequence,保证子节点创建的时序性, 从而也形成了每个客户端的全局时序。
这里写图片描述

- 集群管理
这通常用于那种对集群中机器状态,机器在线率有较高要求的场景,能够快速对集群中机器变化作出响应。这样的场景中,往往有一个监控系统,实时检测集 群机器是否存活。过去的做法通常是:监控系统通过某种手段(比如ping)定时检测每个机器,或者每个机器自己定时向监控系统汇报"我还活着"。 这种做法可行,但是存在两个比较明显的问题:

① 集群中机器有变动的时候,牵连修改的东西比较多。

② 有一定的延时。

利用ZooKeeper中两个特性,就可以实施另一种集群机器存活性监控系统:

① 客户端在节点 x 上注册一个Watcher,那么如果 x 的子节点变化了,会通知该客户端。

② 创建EPHEMERAL类型的节点,一旦客户端和服务器的会话结束或过期,那么该节点就会消失。

应用集群中,我们常常需要让每一个机器知道集群中或依赖的其他某一个集群中哪些机器是活着的,并且在集群机器因为宕机,网络断链等原因能够不在人工 介入的情况下迅速通知到每一个机器,Zookeeper 能够很容易的实现集群管理的功能,如有多台 Server 组成一个服务集群,那么必须要一个"总管"知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它集群必须知道,从而做出调整重新分配服 务策略。同样当增加集群的服务能力时,就会增加一台或多台 Server,同样也必须让"总管"知道,这就是ZooKeeper的集群监控功能。
这里写图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值