Chapter 9 ZooKeeper 内部原理

  • 群首(leader):做为中心点处理全部对ZooKeeper系统变动的请求,它就像一个定序器,创建了全部对ZooKeeper状态的更新的顺序。
  • 追随者(follower):接收群首所发出更新操做请求,并对这些请求进行处理,以此来保障状态更新操做不会发生碰撞。
  • 观察者(observer):不会参与决策哪些请求可被接受的过程,只是观察决策的结果,观察者的设计只是为了系统的可扩展性。

1、请求、事务和标识符

  • 读请求(exists、getData和getChildren):本地处理
  • 写请求(create、delete和setData):被转发给群首,群首执行相应的请求,并造成状态的更新,称为事务(transaction)node

    • 一个事务为一个单位,也就是说全部的变动处理须要以原子方式执行
    • ZooKeeper集群以事务方式运行,并确保全部的变动操做以原子方式被执行,同时不会被其余事务所干扰
    • 同时一个事务还具备幂等性
    • 当群首产生了一个事务,就会为该事务分配一个标识符,称之为ZooKeeper会话ID(zxid),经过Zxid对事务进行标识,就能够按照群首所指定的顺序在各个服务器中按序执行。
    • 服务器之间在进行新的群首选举时也会交换zxid信息,这样就能够知道哪一个无端障服务器接收了更多的事务,并能够同步他们之间的状态信息。
    • zxid为一个long型(64位)整数,分为两部分:时间戳(epoch)部分和计数器(counter)部分,每一个部分为32位。
    • zab协议,经过该协议来广播各个服务器的状态变动信息

2、群首选举

设置群首的目的是为了对客户端所发起的ZooKeeper状态变动请求进行排序,包括:create、setData和delete操做。算法

群首将每个请求转换为一个事务,将这些事务发送给追随者,确保集群按照群首肯定的顺序接受并处理这些事务。apache

LOOKING --> LEADING --> FOLLOWING服务器

当一个服务器收到一个投票信息,该服务器将会根据如下规则修改本身的投票信息:网络

  • 一、将接收的voteId和voteZxid做为一个标识符,并获取接收方当前的投票中的zxid,用myZxid和mySid表示接收方服务器本身的值。
  • 二、若是(voteZxid > myZxid)或者(voteZxid = myZxid且voteId > mySid),保留当前的投票信息。
  • 三、不然,修改本身的投票信息,将voteZxid赋值给myZxid,将voteId赋值给mySid。
简而言之,只有最新的服务器将赢得选举,由于其拥有最近一次的zxid。

【图9-1:群首选举过程的示例】
clipboard.pngsession

图9-1展现了三个服务器,这三个服务器分别以不一样的初始投票值开始,其投票值取决于该服务器的标识符和其最新的zxid。每一个服务器会收到另外两个服务器发送的投票信息,在第一轮以后,服务器s2和服务器s3将会改变其投票值为(1,6),以后服务器服务器s2和服务器s3在改变投票值以后会发送新的通知消息,在接收到这些新的通知消息后,每一个服务器收到的仲裁数量的通知消息拥有同样的投票值,最后选举出服务器s1为群首。

【图9-2:消息交错致使一个服务器选择了另外一个群首】
clipboard.png数据结构

在图9-2中,展现了另外一种状况的例子。服务器 s2 作出了错误判断,选举了另外一个服务器 s3 而不是服务器 s1 ,虽然 s1 的zxid值更高,但在从服务器 s1 向服务器 s2 传送消息时发生了网络故障致使长时间延迟,与此同时,服务器 s2 选择了服务器s 3 做为群首,最终,服务器 s1 和服务器 s3 组成了仲裁数量(quorum),并将忽略服务器 s2 。

【图9-3:群首选举时的长延迟】
clipboard.png并发

若是想实现一个新的群首选举的算法,须要实现一个quorum 包中的Election接口。为了可让用户本身选择群首选举的实现,代码中使用了简单的整数标识符(请查看代码中QuorumPeer.createElectionAlgorithm()),另外两种可选的实现方式为LeaderElection类和AuthFastLeaderElection类

3、Zab:状态更新的广播协议

在接收到一个写请求操做后,追随者会将请求转发给群首,群首将探索性地执行该请求,并将执行结果以事务的方式对状态更新进行广播。

一个事务中包含服务器须要执行变动的确切操做,当事务提交时,服务器就会将这些变动反馈到数据树上,其中数据树为ZooKeeper用于保存状态信息的数据结构(请参考DataTree类)。ide

以后须要面对的问题即是服务器如何确认一个事务是否已经提交,由此引入了所采用的协议:
Zab:ZooKeeper原子广播协议(ZooKeeper Atomic Broadcast protocol)。oop

假设如今有一个活动的群首服务器,并拥有仲裁数量的追随者支持该群首的管理权,经过该协议提交一个事务很是简单,相似于一个两阶段提交。

【图9-4:提交提案的常规消息模式】
clipboard.png

  • 一、群首向全部追随者发送一个PROPOSAL消息p。
  • 二、当一个追随者接收到消息p后,会响应群首一个ACK消息,通知群首其已接受该提案(proposal)。
  • 三、当收到仲裁数量的服务器发送的确认消息后(该仲裁数包括群首本身),群首就会发送消息通知追随者进行提交(COMMIT)操做。
在应答提案消息以前,追随者还须要执行一些检查操做。追随者将会检查所发送的提案消息是否属于其所追随的群首,并确认群首所广播的提案消息和提交事务消失的顺序正确。

【Zab保障了如下几个重要属性:】

  • 若是群首按顺序广播了事务T和事务T,那么每一个服务器在提交T事务前保证事务T已经提交完成。
  • 若是某个服务器按照事务T、事务T的顺序提交事务,全部其余服务器也必然会在提交事务T前提交事务T。

第一个属性保证事务在服务器之间的传送顺序的一致,而第二个竖向地保证服务器不会跳过任何事务。

多个并发的群首可能会致使服务器提交事务的顺序发生错误,或者直接跳过了某些事务。为了阻止系统中同时出现两个服务器自认为本身是群首的状况是很是困难的,时间问题或消息丢失均可能致使这种状况,所以广播协议并不能基于以上假设。

【为了解决这个问题,Zab协议提供了如下保障】:

  • 一个被选举的群首确保在提交完全部以前的时间戳内须要提交的事务,以后才开始广播新的事务。
  • 在任什么时候间点,都不会出现两个被仲裁支持的群首。

第一个需求,群首并不会立刻处于活动状态,直到确保仲裁数量的服务器承认这个群首新的时间戳值。一个时间戳的最初状态必
须包含全部的以前已经提交的事务,或者某些已经被其余服务器接受,但还没有提交完成的事务。这一点很是重要,在群首进行时间戳e的任何新的提案前,必须保证自时间戳开始值到时间戳e-1内的全部提案被提交。若是一个提案消息处于时间戳e'<e,在群首处理时间戳e的第一个提案消息前没有提交以前的这个提案,那么旧的提案将永远不会被提交。

第二个需求有些棘手,由于并不能彻底阻止两个群首独立地运行。假如一个群首l管理并广播事务,在此时,仲裁数量的服务器Q判断群首l已经退出,并开始选举了一个新的群首l',假设在仲裁机构Q放弃群首l时有一个事务T正在广播,并且仲裁机构Q的一个严格的子集记录了这个事务T,在群首l'被选举完成后,在仲裁机构Q以外服务器也记录了这个事务T,为事务T造成一个仲裁数量,在这种状况下,事务T在群首l'被选举后会进行提交。不用担忧这种状况,这并非个bug,Zab协议保证T做为事务的一部分被群首l'提交,确保群首l'的仲裁数量的支持者中至少有一个追随者确认了该事务T,其中的关键点在于群首l'和l在同一时刻并未得到足够的仲裁数量的支持者。

【图9-5:群首发生重叠的状况】
clipboard.png

在图中,群首l为服务器s 5 ,l'为服务器s 3 ,仲裁机构由s 1 到s 3 组成,事务T的zxid为(1,1)。在收到第二个确认消息以后,服务器s 5 成功向服务器s 4 发送了提交消息来通知提交事务。

以前提到Zab保证新群首l'不会缺失(1,1),如今来看看其中的细节。在新群首l'生效前,它必须学习旧的仲裁数量服务器以前
接受的全部提议,而且保证这些服务器不会继续接受来自旧群首的提议。此时,若是群首l还能继续提交提议,好比(1,1),这条提议必须已经被一个以上的承认了新群首的仲裁数量服务器所接受。咱们知道仲裁数量必须在一台以上的服务器之上有所重叠,这样群首l'用来提交的仲裁数量和新群首l使用的仲裁数量一定在一台以上的服务器上是一致的。所以,l'将(1,1)加入自身的状态并传播给其跟随者。

在群首选举时,选择zxid最大的服务器做为群首。这使得ZooKeeper不须要将提议从追随者传到群首,而只须要将状态从群首传
播到追随者。假设有一个追随者接受了一条群首没有接受的提议。群首必须确保在和其余追随者同步以前已经收到并接受了这条提议。可是,若是选择zxid最大的服务器,将能够完彻底全跳过这一步,能够直接发送更新到追随者。

在时间戳发生转换时,Zookeeper使用两种不一样的方式来更新追随者来优化这个过程:

  • DIFF :追随者滞后于群首很少,群首只须要发送缺失的事务点。由于追随者按照严格的顺序接收事务点,这些缺失的事务点

永远是最近的。

  • SNAP:追随者滞后好久,由于发送完整的快照会增大系统恢复的延时,发送缺失的事务点是更优的选择。但是当追随者滞后太远的状况下,只能选择发送完整快照。

4、观察者

观察者和追随者之间有一些共同点。具体说来,他们提交来自群首的提议。不一样于追随者的是,观察者不参与咱们以前介绍过的选举过程。他们仅仅学习经由INFORM消息提交的提议。因为群首将状态变化发送给追随者和观察者,这两种服务器也都被称为学习者。

【注意:深刻INFORM消息】

由于观察者不参与决定提议接受与否的投票,群首不须要发送提议到观察者,群首发送给追随者的提交消息只包含zxid而不包含提议自己。所以,仅仅发送提交消息给观察者并不能使其实施提议。这是咱们使用INFORM消息的缘由。INFORM消息本质上是包含了正在被提交的提议信息的提交消息。

简单来讲,追随者接受两种消息而观察者只接受一种消息。追随者从一次广播中获取提议的内容,并从接下来的一条提交消息中获取
zxid。相比之下,观察者只获取一条包含已提交提议的内容的INFORM消息。

参与决定那条提议被提交的投票的服务器被称为PARTICIPANT服务器。一个PARTICIPANT服务器能够是群首也能够是追随者。而观察者则被称为OBSERVER服务器。

  • 引入观察者的一个主要缘由是提升读请求的可扩展性。
    经过加入多个观察者,能够在不牺牲写操做的吞吐率的前提下服务更多的读操做。写操做的吞吐率取决于仲裁数量的大小。若是加入更多的参与投票的服务器,将须要更大的仲裁数量,而这将减小写操做的吞吐率。增长观察者也不是彻底没有开销的。每个新加入的观察者将对应于每个已提交事务点引入的一条额外消息。然而,这个开销相对于增长参与投票的服务器来讲小不少。
  • 采用观察者的另一个缘由是进行跨多个数据中心的部署。
    因为数据中心之间的网络连接延时,将服务器分散于多个数据中心将明显地下降系统的速度。引入观察者后,更新请求可以先以高吞吐率和低延迟的方式在一个数据中心内执行,接下来再传播到异地的其余数据中心获得执行。值得注意的是,观察者并不能消除数据中心之间的网络消息,由于观察者必须转发更新请求给群首而且处理INFORM消息。不一样的是,当参与的服务器处于同一个数据中心时,观察者保证提交更新必需的消息在数据中心内部获得交换。

5、服务器的构成

群首、追随者和观察者根本上都是服务器。
在实现服务器时使用的主要抽象概念是请求处理器。请求处理器是对处理流水线上不一样阶段的抽象。每个服务器实现了一个请求处理器的序列。能够把一个处理器想象成添加到请求处理的一个元素。一条请求通过服务器流水线上全部处理器的处理后被称为获得彻底处理。

【注意:请求处理器】
ZooKeeper代码里有一个叫RequestProcessor的接口。
这个接口的主要方法是processRequest,它接受一个Request参数。在一条请求处理器的流水线上,对相邻处理器的请求的处理一般经过队列现实解耦合。当一个处理器有一条请求须要下一个处理器进行处理时,它将这条请求加入队列。而后,它将处于等待状态直到下一个处理器处理完此消息。

一、独立服务器

Zookeeper中最简单的流水线是独立服务器(ZeeKeeperServer类)。图9-6描述了此类服务器的流水线。它包含三种请求处理器:PrepRequestProcessor、SyncRequestProcessor和FinalRequestProcessor。
【图9-6:一个独立服务器的流水线】:
clipboard.png

  • PrepRequestProcessor:接受客户端的请求并执行这个请求,处理结果则是生成一个事务。咱们知道事务是执行一个操做的结果,该操做会反映到ZooKeeper的数据树上。事务信息将会以头部记录和事务记录的方式添加到Request对象中。同时还要注意,只有改变ZooKeeper状态的操做才会产生事务,对于读操做并不会产生任何事务。所以,对于读请求的Request对象中,事务的成员属性的引用值则为null。
  • SyncRequestProcessor:负责将事务持久化到磁盘上。实际上就是将事务数据按顺序追加到事务日志中,并生成快照数据。
  • FinalRequestProcessor:若是Request对象包含事务数据,该处理器将会接受对ZooKeeper数据树的修改,不然,该处理器会从数据树中读取数据并返回给客户端。

二、群首服务器

当切换到仲裁模式时,服务器的流水线则有一些变化,首先咱们群首的操做流水线(类LeaderZooKeeper),如图9-7所示。
【图9-7:群首服务器的流水线】
clipboard.png

  • PrepRequestProcessor:
  • ProposalRequestProcessor:该处理器会准备一个提议,并将该提议发送给跟随者。ProposalRequestProcessor将会把全部请求都转发给CommitRequestProcessor,并且,对于写操做请求,还会将请求转发给SyncRequestProcessor处理器。
  • SyncRequestProcessor:处理器所执行的操做与独立服务器中的同样,即持久化事务到磁盘上。执行完以后会触发AckRequestProcessor处理器。
  • AckRequestProcessor:这个处理器是一个简单请求处理器,它仅仅生成确认消息并返回给本身。以前曾提到过,在仲裁模式下,群首须要收到每一个服务器的确认消息,也包括群首本身,而AckRequestProcessor处理器就负责这个。
  • CommitRequestProcessor:CommitRequestProcessor会将收到足够多的确认消息的提议进行提交。实际上,确认消息是由Leader类处理的(Leader.processAck()方法),这个方法会将提交的请求加入到CommitRequestProcessor类中的一个队列中。这个队列会由请求处理器线程进行处理。
  • FinalRequestProcessor:它的做用与独立服务器同样。FinalRequestProcessor处理更新类型的请求,并执行读取请求。在FinalRequestProcessor处理器以前还有一个简单的请求处理器,这个处理器会从提议列表中删除那些待接受的提议,这个处理器的名字叫ToBeAppliedRequestProcessor。待接受请求列表包括那些已经被仲裁法定人数所确认的请求,并等待被执行。群首使用这个列表与追随者之间进行同步,并将收到确认消息的请求加入到这个列表中。以后ToBeAppliedRequestProcessor处理器就会在FinalRequestProcessor处理器执行后删除这个列表中的元素。
    注意,只有更新请求才会加入到待接受请求列表中,而后由ToBeAppliedRequest-Processor处理器从该列表移除。ToBeAppliedRequestProcessor处理器并不会对读取请求进行任何额外的处理操做,而是由FinalRequestProcessor处理器进行操做。

三、追随者和观察者服务器

【图9-8:追随者服务器的流水线】
clipboard.png

  • FollowerRequestProcessor:首先从FollowerRequestProcessor处理器开始,该处理器接收并处理客户端请求。FollowerRequestProcessor处理器以后转发请求给CommitRequestProcessor,同时也会转发写请求到群首服务器。
  • CommitRequestProcessor:会直接转发读取请求到FinalRequestProcessor处理器,并且对于写请求,CommitRequestProcessor在转发给FinalRequestProcessor处理器以前会等待提交事务。
为了保证执行的顺序,CommitRequestProcessor处理器会在收到一个写请求处理器时暂停后续的请求处理。这就意味着,在一个写请求以后接收到的任何读取请求都将被阻塞,直到读取请求转给CommitRequestProcessor处理器。经过等待的方式,请求能够被保证按照接收的顺序来被执行。
  • SyncRequestProcessor:当群首接收到一个新的写请求操做时,直接地或经过其余追随者服务器来生成一个提议,以后转发到追随者服务器。当收到一个提议,追随者服务器会发送这个提议到SyncRequestProcessor处理器。
  • SendRequestProcessor:会向群首发送确认消息。当群首服务器接收到足够确认消息来提交这个提议时,群首就会发送提交事务消息给追随者(同时也会发送INFORM消息给观察者服务器)。当接收到提交事务消息时,追随者就经过CommitRequestProcessor处理器进行处理。

6、本地存储

一、日志和磁盘的使用

服务器经过事务日志来持久化事务。在接受一个提议时,一个服务器(追随者或群首服务器)就会将提议的事务持久化到事物日志中,该事务日志保存在服务器的本地磁盘中,而事务将会按照顺序追加其后。服务器会时不时地滚动日志,即关闭当前文件并打开一个
新的文件。

【组提交和补白】:由于写事务日志是写请求操做的关键路径,所以ZooKeeper必须有效处理写日志问题。通常状况下追加文件到磁盘都会有效完成,但还有一些状况可使ZooKeeper运行的更快,组提交和补白。组提交(GroupCommits)是指在一次磁盘写入时追加多个事务。这将使持久化多个事物只须要一次磁道寻址的开销。

【冲刷(Flush)事务到磁盘介质】:冲刷在这里就是指告诉操做系统将脏页写入磁盘,并在操做完成后返回。由于在SyncRequestProcessor处理器中持久化事务,因此这个处理器同时也会负责冲刷。在SyncRequestProcessor处理器中当须要冲刷事务到磁盘时,事实上是冲刷的是全部队列中的事务,以实现组提交的优化。若是队列中只有一个事务,这个处理器依然会执行冲刷。该处理器并不会等待更多的事务进入队列,由于这样作会增长执行操做的延时。

【补白(padding)】:指在文件中预分配磁盘存储块。这样作,对于涉及存储块分配的文件系统元数据的更新,就不会显著影响文件的顺序写入操做。假如须要高速向日志中追加事务,而文件中并无原先分配存储块,那么不管什么时候在写入操做到达文件的结尾,文件系统都须要分配一个新存储块。而经过补白至少能够减小两次额外的磁盘寻址开销:一次是更新元数据;另外一次是返回文件。

二、快照

快照是ZooKeeper数据树的拷贝副本,每个服务器会常常以序列化整个数据树的方式来提取快照,并将这个提取的快照保存到文件中。服务器在进行快照时不须要进行协做,也不须要暂停处理请求。由于服务器在进行快照时还会继续处理请求,因此当快照完成时,数据树可能又发生了变化,称这样的快照是模糊的(fuzzy),由于它们不能反映出在任意给点的时间点数据树的准确状态。

【举例说明】

一个数据树中只有2个znode节点:/z和/z'。一开始,两个znode节点的数据都是1。
如今有如下操做步骤:
一、开始一个快照。
二、序列化并将/z=1到到快照。
三、使/z的数据为2(事务T)。
四、使/z'的数据为2(事务T')。
五、序列化并将/z'=2写入到快照。

这个快照包含了/z=1和/z'=2。然而,数据树中这两个znode节点在任意的时间点上都不是这个值。这并非问题,由于服务器会重播(replay)事务。每个快照文件都会以快照开始时最后一个被提交的事务做为标记(tag),将这个时间戳记为TS。若是服务器最后加载快照,它会重播在TS以后的全部事务日志中的事务。在这个例子中,它们就是T和T。在快照的基础上重放T和T'后,服务器最终获得/z=2和/z'=2,即一个合理的状态。

接下来还须要考虑一个重要的问题,就是再次执行事务T'是会有问题,由于这个事务在开始快照开始以后已经被接受,而结果也被
快照中保存下来。就像以前所说的,事务是幂等的(idempotent),因此即便按照相同的顺序再次执行相同的事务,也会获得相同的结果,即使其结果已经保存到快照中。

为了理解这个过程,假设重复执行一个已经被执行过的事务。如上例中所描述,一个操做设置某个znode节点的数据为一个特定的值,这个值并不依赖于任何其余东西,无条件(unconditionly)地设置/z'的值(setData请求中的版本号为-1),从新执行操做成功,但由于递增了两次,因此最后以错误的版本号结束。
如如下方式就会致使问题出现,假设有以下3个操做并成功执行:

setData /z', 2, -1
setData /z', 3, 2
setData /a, 0, -1
第一个setData操做跟以前描述的同样,然后又加上了2个setData操做,以此来展现在重放中第二个操做由于错误的版本号而未能
成功的状况。假设这3个操做在提交时被正确执行。此时若是服务器加载最新的快照,即该快照已包含第一个setData操做。服务器仍然会重放第一个setData操做,由于快照被一个更早的zxid所标记。由于从新执行了第一个setData操做。而第二个setData操做的版本号又与指望不符,那么这个操做将没法完成。而第三个setData操做能够正常完成,由于它也是无条件的。

在加载完快照并重放日志后,此时服务器的状态是不正确的,由于它没有包括第二个setData请求。这个操做违反了持久性和正确性,以及请求的序列应该是完好口(no gap)的属性。

这个重放请求的问题能够经过把事务转换为群首服务器所生成的state delta来解决。当群首服务器为一个请求产生事务时,做为事务生成的一部分,包括了一些在这个请求中znode节点或它的数据变化的值(delta值),并指定一个特定的版本号。最后从新执行一个事务就不会致使不一致的版本号。

7、服务器与会话

会话(Session)是Zookeeper的一个重要的抽象。保证请求有序、临时znode节点、监事点都与会话密切相关。所以会话的跟踪机制对ZooKeeper来讲也很是重要。

ZooKeeper服务器的一个重要任务就是跟踪并维护这些会话。

  • 在独立模式下,单个服务器会跟踪全部的会话。
  • 在仲裁模式下,则由群首服务器来跟踪和维护。

群首服务器和独立模式的服务器实际上运行相同的会话跟踪器(参考SessionTracker类和SessionTrackerImpl类)。而追随
者服务器仅仅是简单地把客户端链接的会话信息转发给群首服务器(参考LearnerSessionTracker类)。

为了保证会话的存活,服务器须要接收会话的心跳信息。心跳的形式能够是一个新的请求或者显式的ping消息(参考LearnerHandler.run())。两种状况下,服务器经过更新会话的过时时间来触发(touch)会话活跃(参考SessionTrackerImpl.touchSession()方法)。
在仲裁模式下,群首服务器发送一个PING消息给它的追随者们,追随者们返回自从最新一次PING消息以后的一个session列表。群首服务器每半个tick(参考10.1.1节的介绍)就会发送一个ping消息给追随者们。因此,若是一个tick被设置成2秒,那么群首服务器就会每一秒发送一个ping消息。

对于管理会话的过时有两个重要的要点。一个称为 过时队列(expiry queue)的数据结构(参考ExpiryQueue类),用于维护会话的过时。这个数据结构使用 bucket 来维护会话,每个bucket对应一个某时间范围内过时的会话,群首服务器每次会让一个bucket的会话过时。为了肯定哪个bucket的会话过时,若是有的话,当下一个底限到来时,一个线程会检查这个expiry queue来找出要过时的bucket。这个线程在底限时间到来以前处于睡眠状态,当它被唤醒时,它会取出过时队列的一批session,让它们过时。固然取出的这批数据也多是空的。
为了维护这些bucket,群首服务器把时间分红一些片断,以expirationInterval为单位进行分割,并把每一个会话分配到它的过时时间对应的bucket里,其功能就是有效地计算出一个会话的过时时间,以向上取正的方式得到具体时间间隔。更具体来讲,就是对下面的表达式进行计算,当会话的过时时间更新时,根据结果来决定它属于哪个bucket。
(expirationTime / expirationInterval + 1) * expirationInterval
举例说明,好比expirationInterval为2,会话的超时时间为10。那么这个会话分配到bucket为12((10/2+1)*2的结果)。注意当触发(touch)这个会话时expirationTime会增长,因此随后须要根据以后的计算会话移动到其余的bucket中。
使用bucket的模式来管理的一个主要缘由是为了减小让会话过时这项工做的系统开销。在一个ZooKeeper的部署环境中,可能其客户端就有数千个,所以也就有数千个会话。在这种场景下要细粒度地检查会话过时是不合适的。若是expirationInterval短的话,那么ZooKeeper就会以这种细粒度的方式完成检查。目前expirationInterval是一个tick,一般以秒为单位。

8、服务器与监视点

为了在服务端管理监视点,ZooKeeper的服务端实现了监视点管理器(watch manager)。一个WatchManager类的实例负责管理当前已被注册的监视点列表,并负责触发它们。全部类型的服务器(包括独立服务器,群首服务器,追随者服务器和观察者服务器)都使用一样的方式处理监视点。
DataTree类中持有一个监视点管理器来负责子节点监控和数据的监控,对于这两类监控,请参考4.2节,当处理一个设置监视点的读请求时,该类就会把这个监视点加入manager的监视点列表。相似的,当处理一个事务时,该类也会查找是否须要触发相应的监视点。若是发现有监视点须要触发,该类就会调用manager的触发方法。添加一个监视点和触发一个监视点都会以一个read请求或者FinalRequestProcessor类的一个事务开始。
在服务端触发了一个监视点,最终会传播到客户端。负责处理传播的为服务端的cnxn对象(参见ServerCnxn类),此对象表示客户端和服务端的链接并实现了Watcher接口。Watch.process方法序列化了监视点事件为必定格式,以便用于网络传送。ZooKeeper客户端接收序列化的监视点事件,并将其反序列化为监视点事件的对象,并传递给应用程序。
监视点只会保存在内存,而不会持久化到硬盘。当客户端与服务端的链接断开时,它的全部监视点会从内存中清除。由于客户端库也会维护一份监视点的数据,在重连以后监视点数据会再次被同步到服务端。

9、客户端

在客户端库中有2个主要的类:ZooKeeperClientCnxn

  • ZooKeeper类:实现了大部分API,写客户端应用程序时必须实例化这个类来创建一个会话。一旦创建起一个会话,ZooKeeper就会使用一个会话标识符来关联这个会话。这个会话标识符其实是由服务端所生成的(参考SessionTrackerImpl类)。
  • ClientCnxn类管理链接到server的Socket链接。该类维护了一个可链接的ZooKeeper的服务器列表,并当链接断掉的时候无缝地切换到其余的服务器。当重连到一个其余的服务器时会使用同一个会话(若是没有过时的话),客户端也会重置全部的监视点到刚链接的服务器上(参考ClientCnxn.SendThread.primeConnection())。重置默认是开启的,可

以经过设置disableAutoWatchReset来禁用。

10、序列化

对于网络传输和磁盘保存的序列化消息和事务,ZooKeeper使用了Hadoop中的Jute来作序列化。现在,该库以独立包的方式被引入,在ZooKeeper代码库中,org.apache.jute就是Jute库。

对于Jute最主要的定义文件为zookeeper.jute。它包含了全部的消息定义和文件记录。下面是一个Jute定义的例子:

module org.apache.zookeeper.txn {
...
class CreateTxn {
    ustring path;
    buffer data;
    vector<org.apache.zookeeper.data.ACL> acl;
    boolean ephemeral;
    int parentCVersion;
    }
...
}
这个例子定义模块,该模块包括一个create事务的定义。同时。这个模块映射到了一个ZooKeeper的包中
相关文章
相关标签/搜索