Bootstrap

Zookeeper

1、Zookeeper介绍

1_Zookeeper概述

Z o o K e e p e r ZooKeeper ZooKeeper 是一个分布式的,开源的分布式应用程序协调服务,是 GoogleChubby 一个开源的实现,是 H a d o o p Hadoop Hadoop H b a s e Hbase Hbase 的重要组件。

它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

作用简述: 维护、协调、管理、监控。

Z o o k e e p e r Zookeeper Zookeeper是一个分布式协调服务,就是为用户的分布式应用程序提供协调服务。

序号功能
1为别的分布式程序服务
2本身就是一个分布式程序
3主从协调 服务器节点动态上下线 统一配置管理 分布式共享锁 统一名称服务
4管理(存储,读取)用户程序提交的数据 并为用户程序提供数据节点监听服务

Z o o k e e p e r Zookeeper Zookeeper 具有如下特点:

特征/保障说明
顺序一致性从一个客户端来的更新请求会被顺序执行。
可靠性服务器保存了消息,那么它就一直都存在,直到被覆盖。
统一视图无论服务器连接到哪个服务器,客户端都将看到相同的服务视图。
实时性ZooKeeper 不能保证两个客户端同时得到刚更新的数据。
及时性系统的客户视图保证在特定时间范围内是最新的
最终一致性客户端看到的数据最终是一致的。
独立性等待无关,不同客户端之间互不影响。
原子性更新要不成功要不失败,没有第三个状态

系统架构,官网:https://zookeeper.apache.org/doc/current/zookeeperOver.html

在这里插入图片描述


2_应用场景

Z o o k e e p e r Zookeeper Zookeeper 有如下的典型应用场景:

应用场景说明
数据发布与订阅数据发布到ZooKeeper节点上,供订阅者动态获取数据,
实现配置信息的集中式管理和动态更新。
统一命名服务通过给 Z o o k e e p e r Zookeeper ZookeeperZNode节点进行统一命名,
各个子系统就可以通过名字获取到节点上相应的资源。
比如服务器的IP地址比较难记,但域名相比之下很容易记住。
统一配置管理将每个子系统都需要的配置文件统一放置到ZNode节点中,
当有配置发生改变时,利用watch通知给各个客户端从而更改配置。
集群状态管理动态地感知ZooKeeper上节点的增加、删除,
从而保证集群下相关节点主、副本数据的一致性。
分布式通知与协调管理子服务的状态,心跳检测,信息推送。
分布式锁对处于不同节点上不同的服务,
如果需要顺序的访问一些资源,可以作为分布式锁。
分布式队列提供顺序控制

3_Zookeeper的集群机制

Z o o k e e p e r Zookeeper Zookeeper 是为其他分布式程序提供服务的,所以本身自己不能随便就挂了,所以 Z o o k e e p e r Zookeeper Zookeeper自身的集群机制就很重要。

Z o o k e e p e r Zookeeper Zookeeper的集群机制采用的是半数存活机制,也就是整个集群节点中有半数以上的节点存活,那么整个集群环境可用。

也就是说们的集群节点最好是奇数个节点。


Zookeeper集群节点的角色

Leader

Leader服务器是 Z o o k e e p e r Zookeeper Zookeeper集群工作的核心,其主要工作如下

  1. 事务请求的唯一调度和处理者,保证集群事务处理的顺序性。
  2. 集群内部各服务器的调度者。

Follower

Follower是 Z o o k e e p e r Zookeeper Zookeeper集群的跟随者,其主要工作如下

  1. 处理客户端非事务性请求(读取数据),转发事务请求给Leader服务器。
  2. 参与事务请求Proposal(提议) 的投票。
  3. 参与Leader选举投票。

Observer

  1. Observer充当观察者角色,观察 Z o o k e e p e r Zookeeper Zookeeper集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给Leader服务器进行处理。

  2. Observer不会参与任何形式的投票,包括事务请求Proposal的投票和Leader选举投票。

  3. Observer不属于法定人数,既不参与选举也不响应提议,也不参与写操作的“过半成功”策略。

  4. 不需要将事务持久化到磁盘,一旦被重启,需要从Leader重新同步整个命名空间。


Z o o k e e p e r Zookeeper Zookeeper集群中Server工作状态

状态说明
LOOKING寻找 Leader 状态,当服务器处于该状态时,
它会认为当前集群中没有 Leader ,因此需要进入Leader 选举状态
FOLLOWING跟随者状态,表明当前服务器角色是 Follower
LEADING领导者状态,表明当前服务器角色是 Leader
OBSERVING观察者状态,表明当前服务器角色是 Observe

个人认为:如果双数集群,当宕机一半时,无法选举造成不可用,也算作一种状态。


集群中个服务器之间通信

Leader 服务器会和每一个Follower/Observer 服务器都建立 TCP 连接,同时为每个Follower/Observer 都创建一个叫做 LearnerHandler 的实体。

  • LearnerHandler 主要负责 Leader 和Follower/Observer 之间的网络通讯,包括数据同步,请求转发和 Proposal(提议) 的投票等。

  • Leader 服务器保存了所有 Follower/ObserverLearnerHandler


4_写操作流程

  1. Client 想向 Z o o K e e p e r ZooKeeper ZooKeeper 的 Server1 上写数据,必须先发送一个写的请求。
  2. 如果Server1不是Leader,那么Server1 会把接收到的请求进一步转发给 Leader
  3. 这个Leader 会将写请求广播给各个Server,各个Server写成功后就会通知 Leader
  4. Leader 收到半数以上的 Server 数据写成功了,那么就说明数据写成功了。
  5. 随后,Leader 会告诉Server1数据写成功了。
  6. Server1会反馈通知 Client 数据写成功了,整个流程结束。

2、Zookeeper的选举机制

为什么要进行Leader选举?

Leader 主要作用是保证分布式数据一致性,即每个节点的存储的数据同步。

遇到以下两种情况需要进行Leader选举:

  • 服务器初始化启动。
  • 服务器运行期间无法和Leader保持连接,Leader节点崩溃,逻辑时钟崩溃。

1_服务器初始化时Leader选举

Z o o k e e p e r Zookeeper Zookeeper 由于其自身的性质,一般建议选取奇数个节点进行搭建分布式服务器集群。

以3个节点组成的服务器集群为例,说明服务器初始化时的选举过程。

启动第一台安装 Z o o k e e p e r Zookeeper Zookeeper的节点时,无法单独进行选举,启动第二台时,两节点之间进行通信,开始选举Leader。

  1. 每个Server投出一票。 他们两都选自己为Leader,投票的内容为(SIDZXID)。

    SID即Server的id,安装 Z o o k e e p e r Zookeeper Zookeeper时配置文件中所配置的myid

    ZXID,事务id, 为节点的更新程度,ZXID越大,代表Server对Znode的操作越新。

    由于服务器初始化, 每个Sever上的Znode为0,所以Server1投的票为(1,0),Server2为(2,0)。

    两Server将各自投票发给集群中其他机器。

  2. 每个Server接收来自其他Server的投票。

    集群中的每个Server先判断投票有效性,

    如检查是不是本轮的投票,是不是来Looking状态的服务器投的票。

  3. 对投票结果进行处理。先了解下处理规则

    • 首先对比ZXID。ZXID大的服务器优先作为Leader

    • ZXID相同,比如初始化的时候,每个Server的ZXID都为0,就会比较myidmyid 大的选出来做Leader。

    对于Server而言,他接受到的投票为(2,0),因为自身的票为(1,0),所以此时它会选举Server2为Leader,将自己的更新为(2,0)。

    而Server2收到的投票为Server1的(1,0)由于比他自己小,Server2的投票不变。

    Server1和Server2再次将票投出,投出的票都为(2,0)。

  4. 统计投票。

    每次投票之后,服务器都会统计投票信息,如果判定某个Server有过半的票数投它,那么该Server将会作为Leader

    对于Server1和Server2而言,统计出已经有两台机器接收了(2,0)的投票信息,此时认为选出了Leader

  5. 改变服务器状态。

    当确定了Leader之后,每个Server更新自己的状态,Leader 将状态更新为LeadingFollower 将状态更新为Following

在这里插入图片描述


2_服务器运行期间的Leader选举

Z o o k e e p e r Zookeeper Zookeeper 运行期间,如果有新的Server加入,或者非Leader的Server宕机,那么Leader将会同步数据到新Server或者寻找其他备用Server替代宕机的Server。

若Leader宕机,此时集群暂停对外服务,开始在内部选举新的Leader。

假设当前集群中有Server1、Server2、Server3三台服务器,Server2为当前集群的Leader,由于意外情况,Server2宕机了,便开始进入选举状态。

过程如下:

  1. 变更状态。其他的非Observer服务器将自己的状态改变为Looking,开始进入Leader选举。

  2. 每个Server发出一个投票(myidZXID),由于此集群已经运行过,所以每个Server上的ZXID可能不同。

    假设Server1的ZXID为145,Server3的为122。

    第一轮投票中,Server1和Server3都投自己,票分别为 ( 1,145 )、( 3,122 ),将自己的票发送给集群中所有机器。

  3. 每个Server接收接收来自其他Server的投票,接下来的步骤与初始化时相同。


3_Leader选举实现细节


1—投票数据结构

每个投票中包含了两个最基本的信息,所推举服务器的SIDZXID,投票(Vote)在 Z o o k e e p e r Zookeeper Zookeeper 中包含字段如下

字段说明
id被推举的Leader的SID
Zxid被推举的Leader事务ID。
electionEpoch逻辑时钟,用来判断多个投票是否在同一轮选举周期中,
该值在服务端是一个自增序列,
每次进入新一轮的投票后,都会对该值进行加1操作。
peerEpoch被推举的LeaderEpoch
state当前服务器的状态。

Epoch > Zxid > Sid

EpochZxid 都可能一致,但是 Sid 一定不一样,这样两张选票一定会 PK 出结果。


2—QuorumCnxManager:网络I/O

每台服务器在启动的过程中,会启动一个QuorumPeerManager,负责各台服务器之间的底层Leader选举过程中的网络通信。

  1. 消息队列

    QuorumCnxManager内部维护了一系列的队列,用来保存接收到的、待发送的消息以及消息的发送器,除接收队列以外,其他队列都按照SID分组形成队列集合,如一个集群中除了自身还有3台机器,那么就会为这3台机器分别创建一个发送队列,互不干扰。

    • recvQueue:消息接收队列,用于存放那些从其他服务器接收到的消息。
    • queueSendMap:消息发送队列,用于保存那些待发送的消息,按照SID进行分组。
    • senderWorkerMap:发送器集合,每个SenderWorker消息发送器,都对应一台远程 Z o o k e e p e r Zookeeper Zookeeper服务器,负责消息的发送,也按照SID进行分组。
    • lastMessageSent:最近发送过的消息,为每个SID保留最近发送过的一个消息。
  2. 建立连接

    为了能够相互投票, Z o o k e e p e r Zookeeper Zookeeper集群中的所有机器都需要两两建立起网络连接。

    QuorumCnxManager在启动时会创建一个ServerSocket来监听Leader选举的通信端口(默认为38882888为客户端端口)。

    开启监听后, Z o o k e e p e r Zookeeper Zookeeper能够不断地接收到来自其他服务器的创建连接请求,在接收到其他服务器的TCP连接请求时,会进行处理。

    为了避免两台机器之间重复地创建TCP连接, Z o o k e e p e r Zookeeper Zookeeper只允许SID大的服务器主动和其他机器建立连接,否则断开连接。

    在接收到创建连接请求后,服务器通过对比自己和远程服务器的SID值来判断是否接收连接请求,如果当前服务器发现自己的SID更大,那么会断开当前连接,然后自己主动和远程服务器建立连接。

    一旦连接建立,就会根据远程服务器的SID来创建相应的消息发送器SendWorker和消息接收器RecvWorker,并启动。

  3. 消息接收与发送。

    • 消息接收:

      由消息接收器RecvWorker负责,由于 Z o o k e e p e r Zookeeper Zookeeper为每个远程服务器都分配一个单独RecvWorker,因此,每个RecvWorker只需要不断地从这个TCP连接中读取消息,并将其保存到 recvQueue 队列中。

    • 消息发送:

      由于 Z o o k e e p e r Zookeeper Zookeeper为每个远程服务器都分配一个单独的SendWorker,因此,每个SendWorker只需要不断地从对应的消息发送队列中获取出一个消息发送即可,同时将这个消息放入lastMessageSent中。

      SendWorker中,一旦 Z o o k e e p e r Zookeeper Zookeeper发现针对当前服务器的消息发送队列为空,那么此时需要从lastMessageSent中取出一个最近发送过的消息来进行再次发送,这是为了解决接收方在消息接收前或者接收到消息后服务器挂了,导致消息尚未被正确处理。

      同时, Z o o k e e p e r Zookeeper Zookeeper 能够保证接收方在处理消息时,会对重复消息进行正确的处理。


3—FastLeaderElection:选举算法核心

核心概念说明:

概念说明
外部投票特指其他服务器发来的投票。
内部投票服务器自身当前的投票。
选举轮次 Z o o k e e p e r Zookeeper Zookeeper服务器Leader选举的轮次,即logicalclock
PK对内部投票和外部投票进行对比来确定是否需要变更内部投票。

  1. 选票管理

    • sendqueue:选票发送队列,用于保存待发送的选票。

    • recvqueue:选票接收队列,用于保存接收到的外部投票。

    • WorkerReceiver:选票接收器。

      其会不断地从QuorumCnxManager中获取其他服务器发来的选举消息,并将其转换成一个选票,然后保存到recvqueue中,在选票接收过程中,如果发现该外部选票的选举轮次小于当前服务器的,那么忽略该外部投票,同时立即发送自己的内部投票。

    • WorkerSender:选票发送器,不断地从sendqueue中获取待发送的选票,并将其传递到底层QuorumCnxManager中。

  2. 算法核心

    在这里插入图片描述

    上图展示了FastLeaderElection模块是如何与底层网络I/O进行交互的,Leader选举的基本流程如下:

    1. 自增选举轮次

      Z o o k e e p e r Zookeeper Zookeeper规定所有有效的投票都必须在同一轮次中,在开始新一轮投票时,会首先对logicalclock进行自增操作。

    2. 初始化选票。

      在开始进行新一轮投票之前,每个服务器都会初始化自身的选票,并且在初始化阶段,每台服务器都会将自己推举为 Leader

    3. 发送初始化选票。

      完成选票的初始化后,服务器就会发起第一次投票。

      Z o o k e e p e r Zookeeper Zookeeper会将刚刚初始化好的选票放入sendqueue中,由发送器WorkerSender负责发送出去。

    4. 接收外部投票。

      每台服务器会不断地从recvqueue队列中获取外部选票。

      如果服务器发现无法获取到任何外部投票,那么就会立即确认自己是否和集群中其他服务器保持着有效的连接。

      如果没有连接,则马上建立连接,如果已经建立了连接,则再次发送自己当前的内部投票。

    5. 判断选举轮次。

      在发送完初始化选票之后,接着开始处理外部投票。

      在处理外部投票时,会根据选举轮次来进行不同的处理。

      • 外部投票的选举轮次大于内部投票。

        若服务器自身的选举轮次落后于该外部投票对应服务器的选举轮次,那么就会立即更新自己的选举轮次(logicalclock)。

        并且清空所有已经收到的投票,然后使用初始化的投票来进行PK以确定是否变更内部投票。

        最终再将内部投票发送出去。

      • 外部投票的选举轮次小于内部投票。

        若服务器接收的外选票的选举轮次落后于自身的选举轮次,那么 Z o o k e e p e r Zookeeper Zookeeper就会直接忽略该外部投票,不做任何处理,并返回步骤4。

      • 外部投票的选举轮次等于内部投票,此时可以开始进行选票PK。

    6. 选票PK。

      在进行选票PK时,符合任意一个条件就需要变更投票。

      • 若外部投票中推举的Leader服务器的选举轮次大于内部投票,那么需要变更投票。
      • 若选举轮次一致,那么就对比两者的ZXID,若外部投票的ZXID大,那么需要变更投票。
      • 若两者的ZXID一致,那么就对比两者的SID,若外部投票的SID大,那么就需要变更投票。
    7. 变更投票

      经过PK后,若确定了外部投票优于内部投票,那么就变更投票,即使用外部投票的选票信息来覆盖内部投票,变更完成后,再次将这个变更后的内部投票发送出去。

    8. 选票归档。

      无论是否变更了投票,都会将刚刚收到的那份外部投票放入选票集合recvset中进行归档。

      recvset用于记录当前服务器在本轮次的Leader选举中收到的所有外部投票(按照服务队的SID区别,如{(1, vote1), (2, vote2)…})。

    9. 统计投票。

      完成选票归档后,就可以开始统计投票,统计投票是为了统计集群中是否已经有过半的服务器认可了当前的内部投票,如果确定已经有过半服务器认可了该投票,则终止投票。

      否则返回步骤4。

    10. 更新服务器状态。

      若已经确定可以终止投票,那么就开始更新服务器状态。

      服务器首选判断当前被过半服务器认可的投票所对应的Leader服务器是否是自己。

      若是自己,则将自己的服务器状态更新为LEADING

      若不是,则根据具体情况来确定自己是FOLLOWING或是OBSERVING

以上10个步骤就是FastLeaderElection的核心,其中步骤4-9会经过几轮循环,直到有Leader选举产生。


ZK选举过程个人总结:

关键点说明
网络连接领导选举,在3888端口两两通信。
从一台机器开始传播任何人投票,都会触发那个准 Leader 发起自己的投票。
推选制(变更选票)PK—先比较ZXID,如果相同,再比较SIDmyid)。

3、Zookeeper的使用


1_客户端连接

通过bin目录下的zkCli.sh 命令连接即可

zkCli.sh

zkCli.sh默认连接的是当前节点的Zookeeper节点,如果我们要连接其他节点执行如下命令即可

zkCli.sh -timeout 5000 -server bobo02:2181

2_Zookeeper的数据结构

  1. 层次化的目录结构,命名符合常规文件系统规范
  2. 每个节点在 Z o o k e e p e r Zookeeper Zookeeper中叫做Znode,并且有一个唯一的路径标识
  3. 节点Znode可以包含数据和子节点(但是EPHEMERAL类型的节点不能有子节点)
  4. 客户端应用可以在节点上设置监听器

在这里插入图片描述


3_节点类型

1、znode有两种类型:

节点类型说明
持久节点(Persistent即使客户端断开连接,节点依然存在(断开连接不删除)。
临时节点(Ephemeral客户端会话结束后,节点会被自动删除(断开连接自己删除)。
顺序节点(Sequential Z o o k e e p e r Zookeeper Zookeeper 会为该节点分配一个顺序号,节点名会以顺序号形式命名。
容器节点(Container是一种特殊的持久性节点,用于管理一组相关的子节点。
当容器的最后一个子节点被删除时,该容器将被删除。

知道前两种类型即可,第三种和第四种节点类型是我的一个补充。


2、znode有四种形式的目录节点(默认是persistent)如下

序号节点类型描述
1PERSISTENT持久节点
2PERSISTENT_SEQUENTIAL持久有序节点
3EPHEMERAL短暂节点
4EPHEMERAL_SEQUENTIAL短暂有序节点

创建 Znode 时设置顺序标识,znode 名称后会附加一个值,顺序号是一个单调递增的计数器,有父节点维护在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序。

如果有多个Session创建同一个创建临时节点时让节点有序,则不会进行覆盖创建【Node already exists】,而是后面跟随一个递增的分布式序号(规避覆盖,分布式情况下统一命名)。

在这里插入图片描述


4_节点属性

一个 znode 节点不仅可以存储数据,还有一些其他特别的属性。

节点属性注解
cZxid该数据节点被创建时的事务Id
ctime该数据节点创建时间
mZxid该数据节点被修改时最新的事物Id
mtime该数据节点最后修改时间
pZxid当前节点的父级节点事务Id
cversion子节点版本号(子节点修改次数,每修改一次值+1递增)
dataVersion当前节点版本号(每修改一次值+1递增)
aclVersion当前节点acl版本号(节点被修改acl权限,每修改一次值+1递增)
ephemeralOwner临时节点标识,当前节点如果是临时节点,
则存储的创建者的会话id(sessionId),如果不是,那么值=0
dataLength当前节点所存储的数据长度
numChildren当前节点下子节点的个数
data数据(通常小于 1 MB),二进制安全

Zxid保证事务的顺序一致性

Z o o K e e p e r ZooKeeper ZooKeeper在选举时通过比较各结点的zxid和机器ID选出新的主结点。

ZxidLeader节点生成,有新写入事件时,Leader生成新zxid并随提案一起广播,每个结点本地都保存了当前最近一次事务的 zxid,zxid 是递增的,所以谁的 zxid 越大,就表示谁的数据是最新的。

ZXID的生成规则如下:

在这里插入图片描述

ZXID 由两部分组成:

  • 任期(Epoch):完成本次选举后,直到下次选举前,由同一Leader负责协调写入;
  • 事务计数器:单调递增,每生效一次写入,计数器加一。

ZXID的高 32 位是 Epoch 用来标识朝代变化,比如每次选举 Epoch 都会加改变。

可以理解为当前集群所处的年代或者周期,每个 Leader 就像皇帝,都有自己的年号,所以每次改朝换代,Leader 变更之后,都会在前一个年代的基础上加 1。

这样就算旧的 Leader 崩溃恢复之后,也没有人听它的了。

ZXID的低 32 位是计数器,所以同一任期内,ZXID是连续的,每个结点又都保存着自身最新生效的ZXID,通过对比新提案的ZXID与自身最新ZXID是否相差“1”,来保证事务严格按照顺序生效的。


5_常用命令

推荐使用 help 查看命令使用,因为不同版本会有明显差异。

命令描述语法示例
help查看 Z o o k e e p e r Zookeeper Zookeeper 命令helphelp
create创建一个新节点。
-s 创建顺序节点,
-e 创建临时节点,
-c 创建容器节点,
-t 设置 TTL。
create [-s] [-e] [-c] [-t ttl] path [data] [acl]create -e /tmpNode "data" world:anyone:crwda
delete删除指定路径节点,
(不能有子节点)
-v 指定版本号,
用于条件删除。
delete [-v version] pathdelete -v 1 /myNode
deleteall递归删除指定节点及其所有子节点。deleteall pathdeleteall /myNode
get获取指定路径的节点数据。
-s 显示节点状态信息,
-w 添加监听器。
get [-s] [-w] pathget -s /myNode
set更新指定节点的数据。
-s 显示节点状态信息,
-v 指定版本号。
set [-s] [-v version] path dataset -s -v 1 /myNode "newData"
ls列出指定路径节点的子节点。
-s 显示节点状态信息,
-w 添加监听器,
-R 递归列出所有子节点。
ls [-s] [-w] [-R] pathls -s /myNode
stat获取指定路径节点的统计信息。
(包括版本、修改时间等)
-w 添加监听器。
stat [-w] pathstat -w /myNode
addWatch添加永久性的监听器addWatch [-m mode] pathaddWatch /myNode
[-m mode]可选模式:[PERSISTENT, PERSISTENT_RECURSIVE]之一,
PERSISTENT不监听子节点,
RECURSIVE递归监听子节点,
默认使用递归策略。
addWatch -m PERSISTENT /myNode
removewatches移除指定路径的监听器。
-c 移除子节点监听器,
-d 移除数据监听器,
-a 移除所有监听器,
-l 显示详细信息。
removewatches path [-c /-d /-a] [-l]removewatches /myNode -a -l
printwatches设置是否打印监听器触发的事件,
on 开启,off 关闭。
printwatches on / offprintwatches on
sync确保指定路径的节点数据与所有 Zookeeper 服务器同步。sync pathsync /myNode
connect连接到指定的 Zookeeper 服务器。connect host:portconnect localhost:2181
close关闭当前会话,
但不退出 zkCli 工具。
closeclose
quit退出 zkCli 工具。quitquit
version查看当前
Zookeeper 版本
versionversion
addauth添加认证信息,
scheme 是认证方案(如 digest),
auth 是认证信息。
addauth scheme authaddauth digest user:password
whoami查看当前使用者信息whoamiwhoami
setAcl设置指定节点的ACL
(访问控制列表)。
-s 显示统计信息,
-v 指定版本号,
-R 递归设置所有子节点的 ACL。
setAcl [-s] [-v version] [-R] path aclsetAcl -R /myNode world:anyone:crwda
getAcl获取指定路径节点的 ACL 信息,
-s 显示统计信息。
getAcl [-s] pathgetAcl -s /myNode
config获取/设置 Zookeeper 配置信息。
-c 只输出版本和连接信息,
-w 用于添加监听器,
-s 用于显示详细信息。
config [-c] [-w] [-s]config -s
reconfig动态修改 Zookeeper 集群配置。
-s 显示详细信息,
-v 指定版本号。
reconfig [-s] [-v version] [options]reconfig -s
[options]可选操作,[-members]
[-add],[-remove],[-file path]
对节点动态变更配置的操作
getAllChildrenNumber获取指定路径节点的所有子节点数量。getAllChildrenNumber pathgetAllChildrenNumber /myNode
getEphemerals获取指定路径下的所有临时节点。getEphemerals pathgetEphemerals /myNode
history显示命令历史记录。historyhistory
redo重新执行之前的命令,
cmdno 是命令编号。
history配合使用
redo cmdnoredo 1

注意事项

[ version ][watch] 是老版本的可选参数,目前使用[-v][-w]代替,ls2也被废弃,变为ls -s

以前的版本还有rmr命令,现在也被废弃了,仅支持deleteall

addWatch 是 3.6.x 以后支持的永久性的递归监视,这些监视在触发时不会删除,并且会以递归方式触发注册Znode以及所有子Znode的更改。

Z o o k e e p e r Zookeeper Zookeeper 的 ACL 包含读(r)、写(w)、创建(c)、删除(d)、管理员(a) 权限的设置。例如,world:anyone:cdrwa 表示所有用户均拥有创建、删除、读取和写入的权限。

reconfig 解决困扰已久的zk扩容,需要每台服务器都重新配置zoo.cfg文件和重启zk的问题,这个功能需要在配置文件中提前开启。


6_Session

Client和 Z o o k e e p e r Zookeeper Zookeeper 集群建立连接,整个session状态变化如图所示:

在这里插入图片描述

如果 Client 因为 Timeout Z o o k e e p e r Zookeeper Zookeeper Server 失去连接,client处在 CONNECTING 状态,会自动尝试再去连接Server,如果在 Session 有效期内再次成功连接到某个Server,则回到 CONNECTED 状态。

如果因为网络状态不好,Client和Server失去联系,client会停留在当前状态,会尝试主动再次连接 Z o o k e e p e r Zookeeper Zookeeper Server。

Client不能宣称自己的Session ExpiredSession Expired是由 Z o o k e e p e r Zookeeper Zookeeper Server来决定的,Client可以选择自己主动关闭Session

连接客户端的时候,会消耗一个事务id(简称Session也要消耗事务id即Zxid),断开连接也要统一所有节点所以还会消耗一个事务id。

Session会统一视图,所有节点一致,所以假设当前链接的节点当掉了,连到其他 follow 临时节点也不会消失。


7_Watcher事件监听

监听某个节点的数据内容变化,通过 get 命令 带 -w 参数即可,get path watch 实现监控的。

注意监听一次节点只会触发一次(3.6.x支持永久递归监视),如果要实现多次监听,那么可以在触发事件的处理函数中再次追加对节点的监听操作。

子节点的改变:

监听节点下面的子节点的改变。

[zk: localhost:2181(CONNECTED) 14] ls -w /app1
[]

Watcher监听机制的工作原理

Z o o k e e p e r Zookeeper Zookeeper 允许客户端向服务端的某个Znode注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher通知状态和事件类型做出业务上的改变。

可以把Watcher理解成客户端注册在某个Znode上的触发器,当这个Znode节点发生变化时(增删改查),就会触发Znode对应的注册事件,注册的客户端就会收到异步通知,然后做出业务的改变。

在这里插入图片描述

  • Z o o K e e p e r ZooKeeper ZooKeeperWatcher 机制主要包括客户端线程、客户端 WatcherManager Z o o k e e p e r Zookeeper Zookeeper服务器三部分。
  • 客户端向 Z o o K e e p e r ZooKeeper ZooKeeper服务器注册 Watcher 的同时,会将 Watcher 对象存储在客户端的WatchManager中。
  • z o o k e e p e r zookeeper zookeeper服务器触发 watcher 事件后,会向客户端发送通知, 客户端线程从 WatcherManager 中取出对应的 Watcher 对象来执行回调逻辑。

Watcher 特性总结:

特性说明
一次性一个Watch事件是一个一次性的触发器。
一次性触发,客户端只会收到一次这样的信息。
异步 Z o o k e e p e r Zookeeper Zookeeper服务器发送watcher的通知事件到客户端是异步的,
不能期望能够监控到节点每次的变化,
Z o o k e e p e r Zookeeper Zookeeper只能保证最终的一致性,而无法保证强一致性。
轻量级Watcher 通知非常简单,
它只是通知发生了事件,而不会传递事件对象内容。
客户端串行执行客户端 Watcher 回调的过程是一个串行同步的过程。
注册注册 watchergetDataexistsgetChildren方法
触发触发 watchercreatedeletesetData方法

4、Zookeeper Java API使用

如何通过Java代码来操作 Z o o k e e p e r Zookeeper Zookeeper中的数据?

Z o o k e e p e r Zookeeper Zookeeper的安装目录下是有提供相关的Jar依赖的。

在这里插入图片描述

但是我们对于Maven构建项目已经习惯而且是主流,那么我们可以通过maven坐标来管理)。

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <!--对应zookeeper版本-->
    <version>3.7.2</version>
</dependency>

1_连接ZK服务

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.junit.Test;

import java.io.IOException;

public class Test1 {

    private String connectString = "192.168.100.121:2181,192.168.122:2181,192.168.100.122:2181";
	// Session超时时间不应太小,因为会有延迟。
    private int sessionTimeOut = 5000;

    /**
     * 连接Zookeeper服务端
     */
    @Test
    public void test1() throws IOException {
        ZooKeeper zooKeeper = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
            /**
             * 触发监听事件的回调方法
             * @param watchedEvent
             */
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("触发了.....");
            }
        });
        System.out.println("--->" + zooKeeper);
    }
}


2_基本操作

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;

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

public class Test1 {
	//92.168.100.121:2181,192.168.122:2181,192.168.100.122:2181/test 会直接将test作为根。
    private String connectString = "192.168.100.121:2181,192.168.122:2181,192.168.100.122:2181";

    private int sessionTimeOut = 10000;

    ZooKeeper zooKeeper = null;

    /**
     * 连接Zookeeper服务端
     */
    @Before
    public void test1() throws IOException {
        zooKeeper = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
            /**
             * 触发监听事件的回调方法
             * @param watchedEvent
             */
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("触发了.....");
            }
        });
        System.out.println("--->" + zooKeeper);
    }

    /**
     * 创建节点
     */
    @Test
    public void createNode() throws Exception{
        String path = zooKeeper.create("/node1" // 节点路径
                ,"HelloZookeeper".getBytes() // 节点的数据
                , ZooDefs.Ids.OPEN_ACL_UNSAFE // 权限
        , CreateMode.PERSISTENT // 节点类型
        );
        System.out.println(path);
    }

    /**
     * 判断节点是否存在
     */
    @Test
    public void exist() throws  Exception{
        // true表示的是使用Zookeeper中的watch
        Stat stat = zooKeeper.exists("/node1", true);
        if(stat != null){
            System.out.println("节点存在"+ stat.getNumChildren());
        }else{
            System.out.println("节点不存在 ....");
        }
    }

    /**
     * 获取某个节点下面的所有的子节点
     */
    @Test
    public void getChildrens() throws Exception{
        List<String> childrens = zooKeeper.getChildren("/node1", true);
        for (String children : childrens) {
            // System.out.println(children);
            // 获取子节点中的数据
            byte[] data = zooKeeper.getData("/node1/" + children, false, null);
            System.out.println(children+":" + new String(data));
        }
    }

    /**
     * 修改节点的内容
     */
    @Test
    public void setData() throws Exception{
        // -1 不指定版本 自动维护
        Stat stat = zooKeeper.setData("/node1/n1", "666666".getBytes(), -1);
        System.out.println(stat);
    }


    /**
     * 删除节点
     */
    @Test
    public void deleteNode() throws Exception{
        zooKeeper.delete("/node1",-1);
        
    }


}

3_事件监听处理

​ Java程序如何监听 Z o o k e e p e r Zookeeper Zookeeper中的数据的变化?

    /**
     * 监听Node节点下的子节点的变化(下面的过程可能会被面)
     * 1.在main方法中创建Zookeeper客户端的同时就会创建两个线程,一个负责网络连接通信,一个负责监听
     * 2.监听事件就会通过网络通信发送给zookeeper
     * 3.zookeeper获得注册的监听事件后,立刻将监听事件添加到监听列表里
     * 4.zookeeper监听到 数据变化 或 路径变化,就会将这个消息发送给监听线程
     *  *常见的监听
     *      1.监听节点数据的变化:get path [watch]
     *      2.监听子节点增减的变化:ls path [watch]
     * 5.监听线程就会在内部调用process方法(需要我们实现process方法内容)
     */
    @Test
    public void nodeChildrenChange() throws Exception{
        List<String> list = zooKeeper.getChildren("/node1", new Watcher() {

            /**
             *              None(-1),
             *             NodeCreated(1),
             *             NodeDeleted(2),
             *             NodeDataChanged(3),
             *             NodeChildrenChanged(4),
             *             DataWatchRemoved(5),
             *             ChildWatchRemoved(6);
             * @param watchedEvent
             */
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("--->"+ watchedEvent.getType());
            }
        });
        for (String s : list) {
            System.out.println(s);
        }

        Thread.sleep(Integer.MAX_VALUE);
    }

    /**
     * 监听节点内容变更
     */
    @Test
    public void nodeDataChanged() throws Exception{
        byte[] data = zooKeeper.getData("/node1/n1", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("--->" + watchedEvent.getType());
            }
        }, null);
        System.out.println("--->" + new String(data));
        Thread.sleep(Integer.MAX_VALUE);
    }

5、最佳实践及结语

更好地管理和优化系统:

策略说明
合理使用临时节点临时节点适合用于会话管理,持久节点适合用于存储配置等长期数据。
避免大规模数据存储 Z o o k e e p e r Zookeeper Zookeeper 适合存储少量关键数据,不适合作为大数据存储系统。
注意监听器的使用监听器可能导致大量的通知,合理使用监听器可以减少不必要的网络开销。
考虑延迟和吞吐量 Z o o k e e p e r Zookeeper Zookeeper 的性能受网络延迟和集群规模的影响,设计时应考虑这些因素。

A p a c h e Z o o k e e p e r Apache Zookeeper ApacheZookeeper 作为分布式系统的协调服务,提供了强大而灵活的分布式同步原语,帮助开发者构建高可用、高性能的分布式系统。

通过深入理解其核心概念和使用方法,我们可以更好地利用 Z o o k e e p e r Zookeeper Zookeeper 来解决实际的分布式系统问题。

无论是配置管理、分布式锁,还是集群管理, Z o o k e e p e r Zookeeper Zookeeper 都是一个值得依赖的强大工具。

希望本文能够帮助你更好地理解和使用 Z o o k e e p e r Zookeeper Zookeeper。如果有任何问题或需要进一步的讨论,欢迎留言交流!

没有涉及到的点:分布式锁的使用、paxos算法----->ZAB协议、持久化(快照+日志)和 Redis差不多。

;