Zookeeper(二):数据结构以及集群与应用场景

目录

ZooKeeper进阶

目录结构

节点类型

ZK客户端命令行操作

ZooKeeper会话

事件监听原理刨析

事件监听Watcher

广播模式刨析

广播模式:

Zookeeper集群的特点

ZK常见的应用场景

ZK API实战

IDEA环境搭建

创建ZooKeeper客户端


ZooKeeper进阶

Zookeeper(二):数据结构以及集群与应用场景_第1张图片

    1. Znode数据结构
  1. ZK有一个最开始的节点 /
  2. ZK的节点叫做znode节点
  3. 每个znode节点都可存储数据
  4. 每个znode节点(临时节点除外)都可创建自己的子节点
  5. 多个znode节点共同形成了znode树
  6. Znode树的维系实在内存中,目的是供用户快速的查询
  7. 每个znode节点都是一个路径(通过路径来定位这个节点)
  8. 每个路径名都是唯一的。

目录结构

层次的,目录型结构,便于管理逻辑关系

znode信息

包含最大1MB的数据信息

记录了zxid等元数据信息

节点类型

znode有两种类型,临时的(ephemeral)和持久的(persistent)

znode支持序列SEQUENTIAL

临时znode

客户端会话结束时,ZooKeeper将该临时znode删除,临时znode没有子节点

持久znode

不依赖于客户端会话,只有当客户端明确要删除该持久znode时才会被删除

znode的类型在创建时确定并且之后不能再修改

有序znode节点被分配唯一单调递增的整数。

        比如:客户端创建有序znode,路径为/task/task-,则ZooKeeper为其分配序号1,并追加到znode节点:

        /task/task-000000001。有序znode节点唯一,同时也可根据该序号查看znode创建顺序。

znode有四种形式的目录节点

PERSISTENT:普通持久

EPHEMERAL:普通临时

PERSISTENT_SEQUENTIAL:顺序持久

EPHEMERAL_SEQUENTIAL:顺序临时

要想执行以下指令,需要先启动zk服务器端,再启动zk客户端

./zkServer.sh start:启动zk服务器端

./zkCli.sh:启动zk客户端

ZK客户端命令行操作

指令

示例

ls查看指令

ls /

create创建节点指令,注意,在创建节点时,要分配初始数据。

create /zk01

 hello

create /zk02 ‘’

get查看节点数据指令

hello  数据

cZxid = 0x2

ctime = Mon May 15 05:58:32 PDT 2017创建节点的时间戳

mZxid = 0x2

mtime = Mon May 15 05:58:32 PDT 2017修改此节点数据的最新时间戳

pZxid = 0x2

cversion = 0

dataVersion = 0数据版本号,每当数据发生变化,版本号递增1

aclVersion = 0

ephemeralOwner = 0x0

dataLength = 5数据大小

numChildren = 0子节点个数

get /zk01

set更新节点数据指令(执行后mtime、dataVersion肯定会放生变化,dataLength可能会变化)

set /zk01 hellozk

delete删除节点

delete /zk01

create指令补充:

  1. 创建子节点
  2. Zk节点分四种类型:分别是:

普通持久节点:

普通临时节点:创建此临时节点的客户端失去和zk连接后,此节点消失.zk是通过临时节点监控哪个服务器挂掉的。

Zookeeper(二):数据结构以及集群与应用场景_第2张图片

顺序持久节点:会根据用户指定的节点路径,自动分配一个递增的顺序号。(顺序节点实现分布式锁的效果,服务器1抢到zk05分配zk050001,服务器2抢到zk05分配zk050002)

顺序临时节点:

  1. create /zk01/node01 hello

2.1.create /zk01 hello

2.2. create –e /zk02 abc
2.3.create –s /zk03 abc

2.4.create –s -e /zk05 abcd

zk050000000003

再创建一个就是:

zk050000000004

quit退出zk客户端

ZooKeeper会话

1、客户端通过TCP协议与独立服务器或者一个集群中的某个服务器建立会话连接。

2、会话提供顺序保障,即同一个会话中的请求以FIFO的顺序执行。如果客户端有多个并发会话,FIFO顺序在多个会话之间未必能够保持。

3、如果连接的Server出现问题,在没有超过Timeout时间时,可以连接其他节点。ZooKeeper客户端透明地转移一个会话到不同的服务器。

4、同一session期内的特性不变

5、当一个会话因某种原因终止,在这个会话期间创建的临时节点将会消失。

Session是由谁来创建的?

Leader:产生一个唯一的session,放到消息队列,让所有server知道

过半机制:保证session创建成功或者失

事件监听原理刨析

Zookeeper(二):数据结构以及集群与应用场景_第3张图片

客户端轮询指定节点下的数据

通过网络轮询,代价很大

基于通知(notification)的机制:

客户端向ZooKeeper注册需要接收通知的znode,

通过对znode设置监视点(watch)来接收通知。监视点是一个单次触发的操作,意即监视点会触发一个通知。

为了接收多个通知,客户端必须在每次通知后设置一个新的监视点。

Zookeeper(二):数据结构以及集群与应用场景_第4张图片

事件监听Watcher

Watcher 在 ZooKeeper 是一个核心功能,Watcher 可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应。

可以设置观察点的操作:exists,getChildren,getData

可以触发观察的操作:create,delete,setData

回调client方法

业务核心代码在哪里?

     client

广播模式刨析

ZooKeeper的核心是原子广播,这个机制保证了各个server之间的信息同步。实现这个机制的协议叫做ZAB协议。

ZAB协议有两种模式:

1.恢复模式:当服务启动或者在领导者崩溃后,ZAB就进入了恢复模式。当领导者被选举出来,且大多数server的完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和follower以及observer具有相同的系统状态

2.广播模式

广播模式需要保证proposal被按顺序处理,因此zk采用了递增的事务id号(zxid)来保证。所有的提议(proposal)都在被提出的时候加上了zxid(比如:0x1000000300000002)。

epoch也称为纪元数字。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,低32位是个递增计数。

广播模式:

Zookeeper集群的特点

Zookeeper(二):数据结构以及集群与应用场景_第5张图片

  • 角色模型
    • 集群状态(可用/不可用)
    • 主从分工
  • 攘其外:
    • 统一视图
      • 会话session
      • 数据模型Znode
        • 目录结构
        • 节点类型
    • 事件监听Watcher
  • 原理:
    • 原子消息广播协议ZAB
      • paxos
        • journalnode
        • Sentinel (redis 哨兵)
        • ZooKeeper  à ZAB
      • zxid ,myid:
      • ZXID:epoch+ID
    • 广播模式原理
    • 恢复模式原理:无主模型:zab: zxid ,myid

无主,无服务。选举过程耗时在200ms之内,一般情况下ZooKeeper恢复服务时间间隔不超过200ms

ZK常见的应用场景

  1. 分布式环境下的统一命名服务

Zookeeper(二):数据结构以及集群与应用场景_第6张图片

  1. 分布式环境下的配置管理

Zookeeper(二):数据结构以及集群与应用场景_第7张图片

  1. 数据发布/订阅
  2. 分布式环境下的分布式锁
  3. 集群管理问题

ZK API实战

IDEA环境搭建

  1. 新建project
  2. 将log4j.properties文件拷贝到项目的src目录
  3. 项目的根目录新建lib文件夹,将jar包拷贝到该目录下。
  4. File->->Libarie

创建ZooKeeper客户端

在该包下建类ZooKeeperTest




import java.io.IOException;

import java.util.List;


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.Ids;

import org.apache.ZooKeeper.ZooKeeper;

import org.apache.ZooKeeper.data.Stat;

import org.junit.After;

import org.junit.Assert;

import org.junit.Before;

import org.junit.Test;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;


public class ZooKeeperTest {

   

    private static final int SESSION_TIMEOUT = 30000;

   

    public static final Logger LOGGER = LoggerFactory.getLogger(ZooKeeperTest.class);

   

    private Watcher watcher =  new Watcher() {


       public void process(WatchedEvent event) {

           LOGGER.info("process : " + event.getType());

       }

    };

   

    private ZooKeeper ZooKeeper;

   

    /**创建ZooKeeper连接,单元测试前先执行该方法

     * @throws IOException

     */

    @Before

    public void connect() throws IOException {

//     indicate : all servers

       ZooKeeper  = new ZooKeeper("192.168.20.52:2181,192.168.20.53:2181,192.168.20.54:2181", SESSION_TIMEOUT, watcher);

    }

   

    /**测试完毕后关闭zk连接

     */

    @After

    public void close() {

       try {

           ZooKeeper.close();

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

    }


    /**

     * 创建 znode

     *  1.CreateMode 

     *  PERSISTENT :持久化

     *  PERSISTENT_SEQUENTIAL

     *  EPHEMERAL :临时

     *  EPHEMERAL_SEQUENTIAL

     *  Access Control List: 访问控制列表

     *  https://baike.baidu.com/item/ACL/362453?fr=aladdin

     *  OPEN_ACL_UNSAFE: ANYONE CAN VISIT

     * 
------------------------------
*/ @Test public void testCreate() { String result = null; try { result = ZooKeeper.create("/zk001", "zk001data-p".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // result = ZooKeeper.create("/zk002", "zk002data-e".getBytes(), // Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); // Thread.sleep(30000); } catch (Exception e) { LOGGER.error(e.getMessage()); //抛出AssertionError Assert.fail(); } LOGGER.info("create result : {}", result); } /** * 删除 */ @Test public void testDelete() { try { ZooKeeper.delete("/zk002", -1); } catch (Exception e) { LOGGER.error(e.getMessage()); //抛出AssertionError Assert.fail(); } } /** * 获取数据 */ @Test public void testGetData() { String result = null; try { byte[] bytes = ZooKeeper.getData("/zk001", null, null); result = new String(bytes); } catch (Exception e) { LOGGER.error(e.getMessage()); Assert.fail(); } LOGGER.info("getdata result------------------------------------------------- : {}", result); } /**查看从哪个zk服务器获取的数据,然后xshell关闭对应的zk服务器(zkServer.sh stop) * 然后分析日志: * @throws Exception */ @Test public void testGetData01() throws Exception { String result = null; try { byte[] bytes = ZooKeeper.getData("/zk001", null, null); result = new String(bytes); } catch (Exception e) { LOGGER.error(e.getMessage()); Assert.fail(); } LOGGER.info("getdata result-----------------1------------------ : {}", result); Thread.sleep(30000); byte[] bytes; try { bytes = ZooKeeper.getData("/zk001", null, null); result = new String(bytes); } catch (KeeperException | InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } LOGGER.info("getdata result-----------------2-------------------- : {}", result); } /** * 注册 */ @Test public void testGetDataWatch() { String result = null; try { System.out.println("get:"); byte[] bytes = ZooKeeper.getData("/zk001", new Watcher() { public void process(WatchedEvent event) { LOGGER.info("testGetDataWatch watch : {}", event.getType()); System.out.println("watcher ok"); //testGetDataWatch(); } }, null); result = new String(bytes); } catch (Exception e) { LOGGER.error(e.getMessage()); Assert.fail(); } LOGGER.info("getdata result------------------------------------------ : {}", result); // wacth NodeDataChanged try { System.out.println("set:"); ZooKeeper.setData("/zk001", "testSetDataWAWWW".getBytes(), -1); System.out.println("set2:"); ZooKeeper.setData("/zk001", "testSetDataWAWWW".getBytes(), -1); } catch (Exception e) { LOGGER.error(e.getMessage()); Assert.fail(); } System.out.println("over"); try { Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } } @Test public void testExists() { Stat stat = null; try { stat = ZooKeeper.exists("/zk001", false); } catch (Exception e) { LOGGER.error(e.getMessage()); Assert.fail(); } Assert.assertNotNull(stat); LOGGER.info("exists result : {}", stat.getCzxid()); } @Test public void testSetData() { Stat stat = null; try { stat = ZooKeeper.setData("/zk001", "testSetData".getBytes(), -1); } catch (Exception e) { LOGGER.error(e.getMessage()); Assert.fail(); } Assert.assertNotNull(stat); LOGGER.info("exists result : {}", stat.getVersion()); } @Test public void testExistsWatch1() { Stat stat = null; try { stat = ZooKeeper.exists("/zk001", true); } catch (Exception e) { LOGGER.error(e.getMessage()); Assert.fail(); } Assert.assertNotNull(stat); try { ZooKeeper.delete("/zk001", -1); } catch (Exception e) { e.printStackTrace(); } } @Test public void testExistsWatch2() { Stat stat = null; try { stat = ZooKeeper.exists("/zk002", new Watcher() { @Override public void process(WatchedEvent event) { LOGGER.info("testExistsWatch2 watch : {}", event.getType()); } }); } catch (Exception e) { LOGGER.error(e.getMessage()); Assert.fail(); } Assert.assertNotNull(stat); try { ZooKeeper.setData("/zk002", "testExistsWatch2".getBytes(), -1); } catch (Exception e) { e.printStackTrace(); } try { ZooKeeper.delete("/zk002", -1); } catch (Exception e) { e.printStackTrace(); } } /** */ @Test public void testGetChild() { try { ZooKeeper.create("/zk/001", "001".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); ZooKeeper.create("/zk/002", "002".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); List list = ZooKeeper.getChildren("/zk", true); for (String node : list) { LOGGER.info("fffffff {}", node); } } catch (Exception e) { LOGGER.error(e.getMessage()); Assert.fail(); } } }

框架实现的客户端和服务器之间的故障转移过程

注册Watcher,查询注册watch,增删改触发watcher

如果是多次增删改,回调方法调用几次?

1、watcher事件是一次性的

2、是增删改触发watcher,但是watcher是线程异步执行

3、watcher可以反复注册

如何平滑反复注册?在回调方法中迭代调用即可。

你可能感兴趣的:(#,Zookeeper,zookeeper)