【分布式】zookeeper 入门

目录

前言

1.功能简介

2.安装

3.ZKshell操作命令

4.基础概念

5.编程demo

5.1 连接到ZooKeeper

5.2 读取操作

5.3 写操作

6.数据一致特点

7.应用场景实例

7.1 集群管理


前言

zookeeper主要用于协调分布式服务、 配置维护、组合管理和命名服务,通过使用类似于文件操作系统的目录结构树来维护元数据。参考官网 https://zookeeper.apache.org/

本文主要介绍zk的主要功能,命令行、数据结构和Java API编程demo

1.功能

集群管理 作为集群的入口,可以查找集群的存活主机列表
服务注册

dubbo zk作为注册中心,服务提供者将服务注册到Zookeeper,

客户端在调用服务之前先到Zookeeper中查找服务,再调用服务。

分布式锁 在多个主机并发捞取任务,为了仅一台及其执行,通过获取分布式锁控制仅一台及其执行任务
配置文件集中管理 统一管理分布式环境中的配置文件
...... ......

2.安装

从官方下载后, 解压后即可使用

  1. 服务配置 /${安装路径}/zookeeper-3.4.14/conf/zoo.cfg,可以配置如clientPort=2181
  2. 服务启动 cd /${安装路径}/soft/zookeeper-3.4.14/bin &&sh zkServer.sh start
  3. 客户端连接 sh zkCli.sh -server localhost:2181

3.ZKshell操作命令

通过sh /${安装路径}/soft/zookeeper-3.4.14/bin/zkCli.sh -server localhost:2181 可以进入zookeeper命令行模式(类似于mysql和redis 客户端连接)

其实ZK就是一个树形数据结构,存放在内存汇总,提供了API可以对这颗树进行查找节点、修改节点的值等等操作。

追根到底就是对数据的增删改查

ZooKeeper's Hierarchical Namespace
树形数据结构
命令 操作
ls path [watch]

查看路径的子节点,

不过不存在返回Node does not exist: /test

create path data acl 创建节点,父path必须存在 类似与创建一个目录
get path [watch] 去一个节点上存储的data值
set path data [version] 设置节点的值
delete path [version] 删除节点 子节点为空
deleteall path 删除所有包括子节点
stat path [watch] 查看节点描述元信息

4.基础概念

Znode数据节点、Watcher监听器、ACL访问控制立标、会话(Session)

4.1 Znode

ZooKeeper's Hierarchical Namespace
树形数据结构

上节有说zk是基于一个树形数据结构,我们都知道树由各个树节点组成,数据节点一一ZNode。如上图,结构特别想linux操作系统的文件系统设计,区别就是zk的将该树存储在内存,而linux存储在磁盘。存储在内存的问题就是会在zk服务重启后原来的数据易丢失,及持久化问题

每个节点上都会保存自己的数据内容,同时还会保存一系列属性信息,Zxid(Transaction Id),version修改次数(版本)等等。

4.1.1 Stat属性

在前面我们已经提到,对于每个ZNode,Zookeeper 都会为其维护一个叫作 Stat 的数据结构,Stat 中记录了这个 ZNode 的属性。

以下是通过zk命令行创建并修改节点后查到的属性

create /test testdata    #创建节点
set /test testdata2      #修改节点
create /test/son son     #创建子节点
get /test                #查看节点数据和属性
属性 样例 含义
cZxid

 0x2

创建该节点的事务ID
ctime

Thu Nov 21 11:10:21 CST 2019

创建时间
mZxid

0x3

最后修改该节点的事务ID (set /test testdata2)
mtime

Thu Nov 21 11:13:28 CST 2019

最后修改时间
pZxid

0x4

 
cversion

1

子节点children版本号(创建了一个自节点)

dataVersion

1

数据修改版本
aclVersion 0 访问控制版本
ephemeralOwner

0x0

if 临时节点,该znode的所有者的会话ID。

else,为零。

dataLength

9

字节长度
numChildren

1

子节点个数

树节点按照持久性维度可以分为持久节点和临时节点,区别在于 临时节点在客户端断开连接后会被删除。临时节点由ephemeralOwner标识,非0的是临时节点。

节点还有一种 顺序节点SEQUENTIAL),会在指定path后面附加一个序号(递增),如创建顺序节点zk_test,会创建一个节点path=zk_test0000000001

String perPath = zk .create("zk_test", "data", ZooDefs.Ids.OPEN_ACL_UNSAFE , CreateMode.PERSISTENT_SEQUENTIAL);

4.2 Watcher

事件通知机制是Zookeeper实现分布式协调服务的重要特性,客户端client可以在指定节点上注册一些Watcher(事件监听器),并且在一些特定事件触发的时候,zk服务端Server会将事件通知到注册事件的客户端,客户端可以定义回调函数针对事件做一些处理类似于Listener观察者模式

事件 触发原因 接受事件操作
创建事件 当前节点被创建 exist()
删除事件 当前节点被删除  
更改事件 当前节点data数据被修改 exist() && getData
子节点事件 自节点数组有改动 getChildren()

4.3 ACL

Zookeeper采用ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。Zookeeper 定义了如下5种权限。

  • CREATE: 创建自节点
  • READ: 读取节点上的数据及其子节点列表
  • WRITE:设置节点上的数据
  • DELETE: 删除子节点
  • ADMIN: 设置ACL权限

4.4 会话(Session)

类似与httpSession,基于TCP连接之上,保存了一些C-S客户-服务端交互的状态。服务端分配sessionID,服务端用此id为key存储了一些客户端信息。

会话开始:客户端启动的时候,会与zk服务端建立TCP长连接,第一次连接时服务端为客户端分配一个sessionID会话的生命周期开始。

会话保持:客户端通过心跳检测与服务器保持有效会话,能够向服务器发送请求并接受响应,同时还能接收服务器的Watch事件通知。 

会话销毁:客户端设置sessionTimeout属性-会话超时时间,当tcp连接断开,只要在sessionTimeout时间内重新连接上集群中任意一台服务器会话仍然有效,当超过此时间还不能重连会话就失效销毁了。

5.编程demo

5.1 连接到ZooKeeper

    // 连接 ZooKeeper 服务器
    private ZooKeeper connectServer() {
        ZooKeeper zk = null;
        try {
            zk = new ZooKeeper(Constant.ZK_CONNECTION_STRING, Constant.ZK_SESSION_TIMEOUT ,
                    new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    if (event .getState() == Event.KeeperState.SyncConnected ) {
                        // 唤醒当前正在执行的线程
                        latch.countDown();
                    }
                }
            });
            // 使当前线程处于等待状态
            latch.await();

        } catch (IOException | InterruptedException e) {
            logger.error("" , e );
        }
        return zk ;
    }

5.2 读取操作

  • exists 读取节点属性
  • getDate 读取节点数据
  • getChildren 读取节点子节点
// 2. 读取操作 exist,getData和getChildren
    private void readDataFormZK(ZooKeeper zooKeeper, String path) {

        try {
            Stat curState = zooKeeper.exists(path, false);
            logger.info("attr of " + path +" is " + curState.toString());//attr of /test is 2,3,1574305821415,1574306008185,1,1,0,0,9,1,4

            byte[] data = zooKeeper.getData(path, false, new Stat());
            logger.info("data of " + path +" is " +new String(data));//data of /test is testdata2

            List<String> children = zooKeeper.getChildren(path, false);
            logger.info("children of " + path +" is " +children.toString());//children of /test is [son]

        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Test
    public void read() {
        ZooKeeper zk = connectServer();
        String path = "/test";
        readDataFormZK(zk, path);
    }

5.3 写操作

    @Test
    public void create() {
        ZooKeeper zk = connectServer();
        try {
            String dataStr = "test";
            byte[] data = dataStr.getBytes();
            //1. 创建一个临时性且有序的 ZNode 其余客户端看不到
            String tempPath = zk .create(Constant.ZK_TEST_PATH, data, ZooDefs.Ids.OPEN_ACL_UNSAFE , CreateMode.EPHEMERAL_SEQUENTIAL);
            //2. 创建一个持久顺序节点
            String perPath = zk .create(Constant.ZK_TEST_PATH, data, ZooDefs.Ids.OPEN_ACL_UNSAFE , CreateMode.PERSISTENT_SEQUENTIAL);
            logger.info("create ephemeral zookeeper node ({} => {})" , tempPath , dataStr );//create ephemeral zookeeper node (/zk_test0000000020 => test)
            logger.info("create persistent zookeeper node ({} => {})" , perPath , dataStr );//create persistent zookeeper node (/zk_test0000000021 => test)
            //3. 修改数据
            Stat current=zk.setData(tempPath,"setDate".getBytes(),0);
            logger.info("new attr of "+tempPath+" from zookeeper is {}.", current);//new attr of /zk_test0000000020 from zookeeper is 72,74,1574321648638,1574321648649,1,0,0,72058819008200718,7,0,72
            logger.info("new date of "+tempPath+" from zookeeper is {}.", new String(zk.getData(tempPath, false, current)));//new date of /zk_test0000000020 from zookeeper is setDate.
            //4. 添加子节点 NoChildrenForEphemerals 临时节点下不能有临时节点
            String childPath = zk.create(perPath+"/child", "child".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            logger.info("child added :" + childPath);//child added :/zk_test0000000021/child
        } catch (KeeperException | InterruptedException e) {
            logger.error("" , e );
        }
    }

6.数据一致特点

ZooKeeper读取和写入操作都很快速,读取比写入快,因为读取的时候可以用较旧的数据,类似与mysql的MVCC多版本控制。ZooKeeper提供了以下一致性保证:

  • 顺序一致性:来自客户端的更新操作将按照发送的顺序执行。
  • 原子性:更新全部成功或全部失败
  • 单个系统映像:无论客户端连接到哪个服务器(集群节点),客户端都将看到该服务的相同视图。
  • 可靠性:一旦应用了更新,该更新将一直持续到客户端覆盖该更新为止。
  • 此保证有两个推论:

如果客户获得成功的返回码,则将应用更新。在某些故障(通信错误,超时等)上,客户端将不知道更新是否已应用。我们会采取措施以最大程度地减少失败,但是只有成功的返回码才能提供保证。 (这在Paxos中称为单调性条件。)
从服务器故障中恢复时,客户端通过读取请求或成功更新看到的任何更新都不会回滚。
及时性:保证系统的客户视图在特定的时间范围内(大约数十秒)是最新的。客户端可以在此范围内看到系统更改,或者客户端将检测到服务中断。

7.应用场景实例

7.1 集群管理

集群的入口 ,如kafka,会将自己的集群节点 写入到zk的/brokers/ids,客户端可以根据brokers/ids下面的临时节点1,获取存活的一个主机进行通信。以下是/brokers/ids/1的内容,可以发现此节点是临时节点

get /brokers/ids/1    // 获取节点数据和属性
// 数据
{
  "listener_security_protocol_map": {
    "PLAINTEXT": "PLAINTEXT"
  },
  "endpoints": [
    "PLAINTEXT://${ip}:9092"
  ],
  "jmx_port": -1,
  "host": "${ip}",
  "timestamp": "1574238458689",
  "port": 9092,
  "version": 4
}
// 属性
cZxid = 0x2b
ctime = Wed Nov 20 16:27:38 CST 2019
mZxid = 0x2b
mtime = Wed Nov 20 16:27:38 CST 2019
pZxid = 0x2b
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x1000575bc430006
dataLength = 192
numChildren = 0

连接Kafka集群仅需要提供zk的地址就可以获取kafka的ip:host列表。上图可以发现这个kafka工具客户端就是读取zk的/brokers/ids。

7.2 服务注册

如dubbo中,可以用zookeeper作为注册中心Registry

Dubbo 架构

如果服务注册中心配置成zookeeper,那服务提供者会在zookeeper上创建/dubbo/demo.DemoService ,发布了服务,

    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <dubbo:service interface="demo.DemoService" ref="demoService"/>

该节点下有[consumers, configurators, routers, providers]

查看服务提供者 ls /dubbo/demo.DemoService/providers

此时只有一个提供者,包含了ip和端口,还有接口、方法、进程号等信息,消费者可以从该服务中心获取这些信息,可以发起RPC调用或者其它方式的调用。

[dubbo://${ip}:20880/demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.6.2&generic=false&interface=demo.DemoService&methods=sayHello&pid=16376&side=provider&timestamp=1574407126770]

发布了92 篇原创文章 · 获赞 14 · 访问量 5842

猜你喜欢

转载自blog.csdn.net/sarafina527/article/details/103162306