ZooKeeper学习笔记二

实战使用

ZooKeeper框架的安装

1. 下载并tar开解压(略)
2. 创建配置文件
创建conf/zoo.cfg 配置文件

tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181

 参数说明:

  1. tickTime: zookeeper中使用的基本时间单位, 毫秒值.
  2. dataDir: 数据目录. 可以是任意目录.
  3. dataLogDir: log目录, 同样可以是任意目录. 如果没有设置该参数, 将使用和dataDir相同的设置.
  4. clientPort: 监听client连接的端口号.

启动ZooKeeper服务器

$ bin/zkServer.sh start

进入 CLI

$ bin/zkCli.sh

停止ZooKeeper服务器

$ bin/zkServer.sh stop

Zookeeper CLI

ZooKeeper 命令行界面(CLI)是用来与 ZooKeeper 集成作开发进行交互的。这是在调试和使用不同的选项时的工作有用。
为了执行ZooKeeper的CLI操作, ZooKeeper服务器首先要启动 (“bin/zkServer.sh start”) , 然后使用 ZooKeeper 客户端 (“bin/zkCli.sh”). 当客户端启动后,可以执行以下操作:(1)创建znodes,(2)获取数据,(3)监视 znode 变化,(4)设置数据,(5)创建 znode 的子 znode,(6)列出一个 znode 的子 znode,(7)检查状态,(8)删除一个 znode

  • 创建Znodes:create /path /data
  • 获取数据:get /path
  • 监视:get /path [watch] 1
  • 设置数据:set /path /data
  • 创建子znode:create /parent/path/subnode/path /data
  • 列出子znode:ls /path
  • 检查状态:stat /path
  • 删除Znode:rmr /path

Java API使用

导包

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.13</version>
</dependency>

常用方法列表

String create(final String path, byte data[], List acl, CreateMode createMode)

参数: 路径、 znode内容,ACL(访问控制列表)、 znode创建类型;

用途:创建znode节点

void delete(final String path, int version)

参数: 路径、版本号;如果版本号与znode的版本号不一致,将无法删除,是一种乐观加锁机制;如果将版本号设置为-1,不会去检测版本,直接删除;

用途:删除节点

Stat exists(final String path, Watcher watcher)

参数: 路径、Watcher(监视器);当这个znode节点被改变时,将会触发当前Watcher

用途:判断znode节点是否存在

Stat exists(String path, boolean watch)

参数: 路径、并设置是否监控这个目录节点,这里的 watcher 是在创建 ZooKeeper 实例时指定的 watcher;

判断znode节点是否存在

Stat setData(final String path, byte data[], int version)

参数: 路径、数据、版本号;如果为-1,跳过版本检查

用途:设置znode上的数据

byte[] getData(final String path, Watcher watcher, Stat stat)

参数: 路径、监视器、数据版本等信息

用途:获取znode上的数据

List getChildren(final String path, Watcher watcher)

参数: 路径、监视器;该方法有多个重载

用途:获取节点下的所有子节点

Zookeeper 常用API

ZooKeeper有一个Java和C绑定的官方API。ZooKeeper社区提供了对于大多数语言(.NET,Python等)的非官方API。使用ZooKeeper的API,应用程序可以连接,互动,操作数据,协调,以及从ZooKeeper集成断开。

连接

源码

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
    throws IOException

源码解析

  1. connectString:zookeeper服务地址,例如“192.168.117.128:2181”
  2. sessionTimeout :超时时间,单位为毫秒
  3. watcher:实现org.apache.zookeeper.Watcher接口的实现类,需实现process(WatchedEvent watchedEvent) 方法

实例

/**
 * 连接zk服务器并创建zk实例
 *
 * @throws InterruptedException
 */
public static void connect() throws InterruptedException {
    final CountDownLatch connectedLatch = new CountDownLatch(1);
    try {
        zooKeeper = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            public void process(WatchedEvent event) {
                if  (event.getState()  ==  Event.KeeperState.SyncConnected) {
                    connectedLatch.countDown();
                }
            }
        });
        System.out.println("服务器连接成功");
    } catch (IOException e) {
        System.out.println("服务器连接失败");
        e.printStackTrace();
    }
    connectedLatch.await();
}

Create:创建同步节点

源码

public String create(final String path, byte data[], List<ACL> acl,
        CreateMode createMode)

源码解析

  1. path:创建节点路径,需保证父节点已存在
  2. data:节点数据
  3. acl:(Access Control List)权限列表
  4. createMode:节点类型

acl权限列表

使用api 中预设的ACL
在创建znode时可以设置该znode的ACL列表。接口org.apache.zookeeper.ZooDefs.Ids中有一些已经设置好的权限常量,例如:

  1. OPEN_ACL_UNSAFE:完全开放
  2. CREATOR_ALL_ACL:创建该znode的连接拥有所有权限
  3. READ_ACL_UNSAFE:所有的客户端都可读

createMode:节点类型

  1. CreateMode.PERSISTENT:持久化节点
  2. CreateMode.EPHEMERAL:临时节点(连接断开自动删除)
  3. CreateMode.PERSISTENT_SEQUENTIAL:持久化有序节点
  4. CreateMode.EPHEMERAL_SEQUENTIAL:临时有序节点(连接断开自动删除)

实例

/**
 * 创建节点
 *
 * @param node
 * @param data
 */
public void create(String node, String data) {
    //创建
    String result = null;
    try {
        result = zooKeeper.create(
                node,
                data.getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
        System.out.println("返回的路径为:" + result);
    } catch (KeeperException e) {
        System.out.println("创建失败,节点已存在");
        e.printStackTrace();
    } catch (InterruptedException e) {
        System.out.println("创建失败:" + "the transaction is interrupted");
        e.printStackTrace();
    }
    System.out.println("创建成功:" + result);
}

getData:获取节点

源码

public byte[] getData(final String path, Watcher watcher, Stat stat)

源码解析

znode节点的状态信息

使用get命令获取指定节点的数据时, 同时也将返回该节点的状态信息, 称为Stat. 其包含如下字段:

  • czxid. 节点创建时的zxid.
  • mzxid. 节点最新一次更新发生时的zxid.
  • ctime. 节点创建时的时间戳.
  • mtime. 节点最新一次更新发生时的时间戳.
  • dataVersion. 节点数据的更新次数.
  • cversion. 其子节点的更新次数.
  • aclVersion. 节点ACL(授权信息)的更新次数.
  • ephemeralOwner. 如果该节点为ephemeral节点, ephemeralOwner值表示与该节点绑定的session id. 如果该节点不是ephemeral节点, ephemeralOwner值为0. 至于什么是ephemeral节点, 请看后面的讲述.
  • dataLength. 节点数据的字节数.
  • numChildren. 子节点个数.

实例

/**
 * 读取节点
 *
 * @param node
 */
public void getData(String node) {
    Stat stat = new Stat();
    try {
        byte[] bytes = zooKeeper.getData(node, watcher, stat);
        System.out.println("数据存在" + bytes.toString());
        System.out.println("stat:" + stat);
    } catch (KeeperException e) {
        System.out.println("数据不存在");
        e.printStackTrace();
    } catch (InterruptedException e) {
        System.out.println("the server transaction is interrupted");
        e.printStackTrace();
    }
}

获取子节点

实例

/**
 * 输出子节点
 * @param node
 */
public void getChildren(String node) {
    try {
        List<String> zooKeeperChildren = zooKeeper.getChildren(node, watcher);
        System.out.println(node + "的子节点共有:" +
                zooKeeperChildren.size() + "个"
        );
        System.out.print("[");
        for (String child : zooKeeperChildren) {
            System.out.print(child + ",");
        }
        System.out.println("]");
    } catch (KeeperException e) {
        System.out.println("节点不存在");
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

注:getChildren方法会在子点有变化时触发watcher 这个监听器

删除节点

/**
 * 删除节点
 *
 * @param node
 */
public void deleteNode(String node) {
    // 删除
    try {
        zooKeeper.delete(node, -1);
        System.out.println("删除节点成功:" + node);
    } catch (KeeperException e) {
        System.out.println("节点不存在");
        e.printStackTrace();
    } catch (InterruptedException e) {
        System.out.println("删除节点失败:the server transaction is interrupted");
        e.printStackTrace();
    }
}

关闭连连接

/**
 * 关闭zk连接
 */
public void close() {
    try {
        zooKeeper.close();
    } catch (InterruptedException e) {
        System.out.println("关闭失败");
        e.printStackTrace();
    }
}

注:一般情况下close()方法放在finally代码块执行

常见异常

InterruptedException 异常

@throws InterruptedException if the transaction is interrupted

若客户端的某操作被中断,则会抛出InterruptedException异常。抛出该异常时,不一定是出现故障,只能表明某个zookeeper操作被中断而已。

KeeperException 异常

@throws KeeperException if the server returns a non-zero error code

服务器发出错误信号或是服务器存在通信故障。该类现在共有21个子类, 分为3大类:

关于KeeperErrorCode = ConnectionLoss错误

在使用Zookeeper API时,常常会引发以下错误 ,这是由于连接还未完成就执行zookeeper的get/create/exists操作引起的

Exception in thread "main" org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /test
at org.apache.zookeeper.KeeperException.create(KeeperException.Java:99)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.exists(ZooKeeper.java:1501)
at org.apache.zookeeper.ZooKeeper.exists(ZooKeeper.java:1529)
at com.jiq.test.ZooKeeperTest.main(ZooKeeperTest.java:12)

解决的办法是等Zookeeper连接初始化完成再使用实例,以下是示例代码

public static ZooKeeper getInstance() throws IOException, InterruptedException {
//--------------------------------------------------------------
// 为避免连接还未完成就执行zookeeper的get/create/exists操作引起的(KeeperErrorCode = ConnectionLoss)
// 这里等Zookeeper的连接完成才返回实例
//--------------------------------------------------------------
    ZooKeeper zk = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
        @Override
        public void process(WatchedEvent event) {
            if (event.getState() == Event.KeeperState.SyncConnected) {
                connectedSignal.countDown();
            }
        }
    });
    connectedSignal.await();
    return zk;
}

注:Zookeeper的监控只有在使用getData(),exists(),getChildren()这几个方法时才会触发watcher

常见问题

Zk基础知识总结

  1. zookeeper是一个开源的分布式协调服务框架。
  2. 应用场景:分布式通知/协调、负载均衡、配置中心、分布式锁、分布式队列等。
  3. 使用ZAB协议,Paxos算法。
  4. 节点类型:持久节点、持久顺序节点、临时节点、临时顺序节点。
  5. 不是永久的,一次性的,需要借助第三方工具实现重复注册。
  6. 部署模式:单机模式、伪集群模式、集群模式。
  7. 集群角色:leader、follower、observer。
  8. 集群规则为2N+1台,N>0,即3台。
  9. 集群需要一半以上的机器可用,所以,3台挂掉1台还能工作,2台不能。
  10. 3.5版本开始支持动态扩容。
  11. java客户端:zk自带的zkclient及Apache开源的Curator。
  12. chubby是google的,完全实现paxos算法,不开源。zookeeper是chubby的开源实现,使用zab协议,paxos算法的变种。
  13. 常用命令:ls get set create delete等。

1.zookeeper是如何保证事务的顺序一致性的

zookeeper采用了递增的事务Id来标识,所有的proposal都在被提出的时候加上了zxid,zxid实际上是一个64位的数字,高32位是epoch用来标识leader是否发生改变,如果有新的leader产生出来,epoch会自增,低32位用来递增计数。

当新产生proposal的时候,会依据数据库的两阶段过程,首先会向其他的server发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行

2.zookeeper是如何选取主leader的?

当leader崩溃或者leader失去大多数的follower,这时zk进入恢复模式,通过节点间广播消息,推举出数据最新的节点,成为 Leader。

3.ZNode 的分类

zk中znode类型有四种:

  • 持久化节点
  • 持久化顺序节点(有顺序 能够在注册机器等许多场景用到)
  • 临时节点
  • 临时顺序节点

4.zk的通知机制

client端会对某个znode建立一个watcher事件,当该znode发生变化时,这些client会收到zk的通知,然后client可以根据znode变化来做出业务上的改变等。

5.zk的配置管理

程序分布式的部署在不同的机器上,将程序的配置信息放在zk的znode下,当有配置发生改变时,也就是znode发生变化时,可以通过改变zk中某个目录节点的内容,利用water通知给各个客户端 从而更改配置。

6.zk的命名服务

命名服务是指通过指定的名字来获取资源或者服务的地址,利用zk创建一个全局的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。

7.分布式通知和协调

对于系统调度来说:操作人员发送通知实际是通过控制台改变某个节点的状态,然后zk将这些变化发送给注册了这个节点的watcher的所有客户端。

对于执行情况汇报:每个工作进程都在某个目录下创建一个临时节点。并携带工作的进度数据,这样汇总的进程可以监控目录子节点的变化获得工作进度的实时的全局情况。

8.机器中为什么会有master

在分布式环境中,有些业务逻辑只需要集群中的某一台机器进行执行,其他的机器可以共享这个结果,这样可以大大减少重复计算,提高性能,于是就需要进行master选举。

2. 分布式协调zookeeper的如何进行领导选举的?

(1)选举原理图:

(2)具体解释如下:

1)每个Server启动以后都询问其它的Server它要投票给谁。

2)对于其他server的询问,server每次根据自己的状态都回复自己推荐的leader的id和上一次处理事务的zxid(系统启动时每个server都会推荐自己)

3)收到所有Server回复以后,就计算出zxid最大的哪个Server,并将这个Server相关信息设置成下一次要投票的Server。

4)计算这过程中获得票数最多的的sever为获胜者,如果获胜者的票数超过半数,则该server被选为leader。否则,继续这个过程,直到leader被选举出来。

Dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,发布者和订阅者之间还能通信么?

可以。因为启动duboo时,消费者会从zookeeper获取注册的生产者的地址接口等数据,缓存在本地。每次调用时,按照本地存储的地址进行调用。

另外,dubbo在健壮性的文档中也有描述:

1)注册中心对等集群,任意一台宕机后,将自动切换懂啊另一台

2)注册中心全部宕机后,服务提供者和服务消费者仍能通过本地缓存通讯

zk的通知机制

client端会对某个znode建立一个watcher事件,当该znode发生变化时,这些client会收到zk的通知,然后client可以根据znode变化来做出业务上的改变等。

zk的配置管理

程序分布式的部署在不同的机器上,将程序的配置信息放在zk的znode下,当有配置发生改变时,也就是znode发生变化时,可以通过改变zk中某个目录节点的内容,利用water通知给各个客户端 从而更改配置。

zk的命名服务

命名服务是指通过指定的名字来获取资源或者服务的地址,利用zk创建一个全局的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。

分布式通知和协调

对于系统调度来说:操作人员发送通知实际是通过控制台改变某个节点的状态,然后zk将这些变化发送给注册了这个节点的watcher的所有客户端。
对于执行情况汇报:每个工作进程都在某个目录下创建一个临时节点。并携带工作的进度数据,这样汇总的进程可以监控目录子节点的变化获得工作进度的实时的全局情况。

机器中为什么会有master;
在分布式环境中,有些业务逻辑只需要集群中的某一台机器进行执行,其他的机器可以共享这个结果,这样可以大大减少重复计算,提高性能,于是就需要进行master选举。

参考:

https://www.cnblogs.com/shengkejava/p/5611671.html

https://www.cnblogs.com/leocook/p/zk_1.html

https://www.zhihu.com/question/35139415

http://ningg.top/zookeeper-lesson-11-zookeeper-application/

http://www.cnblogs.com/leesf456/p/6036548.html

https://www.cnblogs.com/lanqiu5ge/p/9405601.html

https://mp.weixin.qq.com/s/AE_2U5tUjEZSYoLovlcU6g

https://www.w3cschool.cn/zookeeper/zookeeper_fundamentals.html

https://www.cnblogs.com/raphael5200/p/5285583.html

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值