什么是zookeeper

用一句话对其定义就是:它是一套高吞吐的分布式协调系统。从中我们可以知道zookeeper至少具备以下特点:

  1. Zookeeper的主要作用是为分布式系统提供协调服务,包括但不限于:分布式锁,统一命名服务,配置管理,负载均衡,主控服务器选举以及主从切换等。
  2. Zookeeper自身通常也以分布式形式存在。一个Zookeeper服务通常由多台服务器节点构成,只要其中超过一半的节点存活,Zookeeper即可正常对外提供服务,所以Zookeeper也暗含高可用的特性。
  3. Zookeeper是以高吞吐量为目标进行设计的,故而在读多写少的场合有非常好的性能表现。

zookeeper集群角色

在Zookeeper集群中,分别有Leader,Follower和Observer三种类型的服务器角色。

  • Leader
    Leader服务器在整个正常运行期间有且仅有一台,集群会通过选举的方式选举出Leader服务器,由它同统一处理集群的事务性请求以及集群内各服务器的调度。
  • Follower
    Follower的主要职责有以下几点:
  1. 参与Leader选举投票
  2. 参与事务请求Proposal的投票
  3. 处理客户端非事务请求(读),并转发事务请求(写)给Leader服务器。
  • Observer
    Observer是弱化版的Follower。其像Follower一样能够处理非事务也就是读请求,并转发事务请求给Leader服务器,但是其不参与任何形式的投票,不管是Leader选举投票还是事务请求Proposal的投票。引入这个角色主要是为了在不影响集群事务处理能力的前提下提升集群的非事务处理的吞吐量。

zookeeper的数据模型

Zookeeper将数据存储于内存中,具体而言,Znode是存储数据的最小单元。而Znode被以层次化的结构进行组织,形容一棵树。

zookeeper 服务端 客户端缓存 zookeeper服务器功能_客户端

znode的类型

znode按照其生命周期的长短可以分为持久节点(PERSISTENT)和(EPHEMERAL)。在创建的时候还可以选择是否由zookeeper服务端在其路径后添加一个串号用来区分同一个父节点下多个节点创建的先后顺序。经过组合就有以下四种类型:

  1. 持久类型(PERSISTENT)
    最常见的Znode类型,一旦创建将会一直存在于服务端,除非客户端通过删除操作进行删除。持久结点下可以创建子结点。
  2. 持久顺序节点(PERSISTENT_SEQUENTIAL)
    在具有持久结点基本特性的基础上,会通过在结点路径后缀一串序号来区分多个子结点创建的先后顺序。这工作由Zookeeper服务端自动给我们做,只要在创建Znode时指定结点类型为该类型。
  3. 临时节点(EPHEMERAL)
    临时结点的生命周期和客户端会话保持一致。客户端段会话存在的话临时结点也存在,客户端会话断开则临时结点会自动被服务端删除。临时结点下不能创建子结点。
  4. 临时顺序节点(EPHEMERAL_SEQUENTIAL)
    具有临时结点的基本特性,又有顺序性。

zookeeper的基本命令操作

在zookeeper集群搭建,这里已经完成了zookeeper的启动。这里依据其启动后进行相关操作。

  • 查看当前zookeeper状态
./zkServer.sh status
  • 客户端连接zookeeper
./zkCli.sh
  • 查看节点命令
    客户端连接到zookeeper服务之后,可以查看当前服务下的节点,默认有一个名叫zookeeper的节点
ls /
  • 创建节点并设置节点存储的值
    通过create进行创建节点,创建的时候可以同时设置当前创建节点的内容值
create /node1 "124"
  • 获取节点的值
get /node1
  • 获取节点的信息状态
stat /node1

返回如下类似信息

cZxid = 0x860
ctime = Mon Dec 23 07:36:55 UTC 2019
mZxid = 0x860
mtime = Mon Dec 23 07:36:55 UTC 2019
pZxid = 0x860
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0
  • cZxid 创建节点的事务ID
  • ctime 创建节点的时间
  • mZxid 最后一次修改节点的事务ID
  • mtime 修改节点的时间
  • pZxid 子节点最后一次被更新的事务ID
  • cversion 子节点版本
  • dataVersion 当前节点数据版本
  • aclVersion 当前节点权限的版本
  • ephemeralOwner (用于临时节点,保存临时节点的ID(临时节点的会话ID,会话结束的时候会根据这个ID进行删除临时节点),如果为持久行节点,则其值为0)
  • dataLength 数据的长度
  • numChildren 子节点的数量

zookeeper的事件监听机制

Zookeeper中可以通过Watcher来实现事件监听机制。客户端可以向服务端注册Watcher用以监听某些事件,一旦该事件发生,服务端即会向客户端发送一个通知。其主要工作流程如下图所示

zookeeper 服务端 客户端缓存 zookeeper服务器功能_客户端_02


具体而言,Watcher是Zookeeper原生API中提供的事件监听接口,用户要实现事件监听必须实现该接口并重写process(WatchedEvent event)方法,该方法定义了客户端在接收到服务端事件通知后的回调逻辑。

连接状态

  • KeeperStat.Expired 在一定时间内客户端没有收到服务器的通知, 则认为当前的会话已经过期了。
  • KeeperStat.Disconnected 断开连接的状态
  • KeeperStat.SyncConnected 客户端和服务器端在某一个节点上建立连接,并且完成一次version、zxid同步
  • KeeperStat.authFailed 授权失败

KeeperStat.SyncConnected 状态下的几种事件类型

  • NodeCreated 当节点被创建的时候,触发
  • NodeChildrenChanged 表示子节点被创建、被删除、子节点数据发生变化
  • NodeDataChanged 节点数据发生变化
  • NodeDeleted 节点被删除
    节点被删除
  • None 客户端和服务器端连接状态发生变化的时候,事件类型就是None

Zookeeper的事件监听机制有以下特性

  1. 当监听器监听的事件被触发,服务端会发送通知给客户端,但通知信息中不包括事件的具体内容。以监听ZNode结点数据变化为例,当Znode的数据被改变,客户端会收到事件类型为NodeDataChanged的通知,但该Znode的数据改变成了什么客户端无法从通知中获取,需要客户端在收到通知后手动去获取。
  2. Watcher是一次性的。一旦被触发将会失效。如果需要反复进行监听就需要反复进行注册。这么设计是为了减轻服务端的压力,但是对开发者而言却是相当不友好,不过不用着急,可以通过一些Zookeeper的开源客户端轻松实现对某一事件的永久监听。

Zookeeper如何保证分布式数据一致性——ZAB协议

Zookeeper采用ZAB(Zookeeper Atomic Broadcast)协议来保证分布式数据一致性。ZAB并不是一种通用的分布式一致性算法,而是一种专为Zookeeper设计的崩溃可恢复的原子消息广播算法。ZAB协议包括两种基本模式:崩溃恢复模式和消息广播模式。崩溃恢复模式主要用来在集群启动过程,或者Leader服务器崩溃退出后进行新的Leader服务器的选举以及数据同步;消息广播模式主要用来进行事务请求的处理。下面就从这两个方面来介绍

事务请求的处理流程

ZAB协议的核心是定义了对事务请求的处理方式,整个过程可以概括如下:

  1. 所有的事务请求都交由集群的Leader服务器来处理,Leader服务器会将一个事务请求转换成一个Proposal(提议),并为其生成一个全局递增的唯一ID,这个ID就是事务ID,即ZXID,Leader服务器对Proposal是按其ZXID的先后顺序来进行排序和处理的。
  2. 之后Leader服务器会将Proposal放入每个Follower对应的队列中(Leader会为每个Follower分配一个单独的队列),并以FIFO的方式发送给Follower服务器。
  3. Follower服务器接收到事务Proposal后,首先以事务日志的方式写入本地磁盘,并且在成功后返回Leader服务器一个ACK响应。
  4. Leader服务器只要收到过半Follower的ACK响应,就会广播一个Commit消息给Follower以通知其进行Proposal的提交,同时Leader自身也会完成Proposal的提交。

整个过程如下图所示

zookeeper 服务端 客户端缓存 zookeeper服务器功能_zookeeper 服务端 客户端缓存_03

Leader服务器的选举流程

当集群中不存在Leader服务器时集群会进行Leader服务器的选举,这通常存在于两种情况:1.集群刚启动时 2.集群运行时,但Leader服务器因故退出。集群中的服务器会向其他所有的Follower服务器发送消息,这个消息可以形象化的称之为选票,选票主要由两个信息组成,所推举的Leader服务器的ID(即配置在myid文件中的数字),以及该服务器的事务ID,事务表示对服务器状态变更的操作,一个服务器的事务ID越大,则其数据越新。整个过程如下所述:

  1. Follower服务器投出选票(SID,ZXID),第一次每个Follower都会推选自己为Leader服务器,也就是说每个Follower第一次投出的选票是自己的服务器ID和事务ID。
  2. 每个Follower都会接收到来自于其他Follower的选票,它会基于如下规则重新生成一张选票:比较收到的选票和自己的ZXID的大小,选取其中最大的;若ZXID一样则选取SID即服务器ID最大的。最终每个服务器都会重新生成一张选票,并将该选票投出去。

这样经过多轮投票后,如果某一台服务器得到了超过半数的选票,则其将当前选为Leader。由以上分析可知,Zookeeper集群对Leader服务器的选择具有偏向性,偏向于那些ZXID更大,即数据更新的机器。

整个过程如下图所示

zookeeper 服务端 客户端缓存 zookeeper服务器功能_客户端_04

Zookeeper如何进行服务器故障的容错

Zookeeper通过事务日志和数据快照来避免因为服务器故障导致的数据丢失。

  • 事务日志是指服务器在更新内存数据前先将事务操作以日志的方式写入磁盘,Leader和Follower服务器都会记录事务日志。
  • 数据快照是指周期性通过深度遍历的方式将内存中的树形结构数据转入外存快照中。但要注意这种快照是"模糊"的,因为可能在做快照时内存数据发生了变化。但是因为Zookeeper本身对事务操作进行了幂等性保证,故在将快照加载进内存后会通过执行事务日志的方式来讲数据恢复到最新状态。