节点介绍
通过前面的介绍我们知道,zookeeper是一个分布式协作工具。在分布式应用中,很多用于协作的原语常常在很多应用之间共享,因此,设计一个用于协作需求的服务的方法往往是提供原语列表,暴露出每个原语的实例化调用方法,并直接控制这些实例。比如,我们可以说分布式锁机制组成了一个重要的原语,同时暴露出创建(create)、获取(acquire)和释放(release)三个调用方法。
这种设计存在一个重大的缺陷:首先,我们要么预先提出一份详尽的原语列表,要么提供API的扩展,以便引入新的原语,其次,以这种方式实现的服务丧失了灵活性。
因此,在zookeeper中我们另辟蹊径,zookeeper并不直接暴露原语,取而代之,它暴露了由一小部分调用方法组成的类似文件系统的api,以便允许应用实现自己的原语。我们通常使用菜谱(recipes)来表示这些原语的实现。菜谱包括zookeeper操作和维护一个小型的数据节点,这些节点被称为znode,采用类似于文件系统的层级树状结构进行管理。如图描述了一个znode树的结构,根节点包含四个子节点,其中三个子节点拥有下一级节点,叶子节点存储了数据信息。
针对一个znode,没有数据常常表达了重要的信息。比如,在主从模式中,主节点的znode没有数据,表示当前还没有选举出主节点,上图中涉及的一些其它znode节点在主从模式中的配置非常有用:
/workers节点作为父节点,其下每个znode子节点保存了系统中一个可用从节点信息。如上图所示,有一个从节点(foo.com:2181)。
/tasks节点作为父节点,其下每个znode节点保存了所有已经创建并等待从节点执行的任务的信息,主从模式的应用的客户端在/tasks下添加一个znode子节点用来表示一个新任务,并等待任务状态的znode节点。
/assign节点作为父节点其下每个znode子节点保存了分配到某个从节点的一个任务信息,当主节点为某个从节点分配了一个任务,就会在/assign下增加一个子节点。
常用节点API操作
znode节点可能含有数据,也可能没有。如果一个znode节点包含任何数据,那么数据存储为字节数组(byte array)。字节数组的具体格式特定于每个应用的实现,zookeeper并不直接提供解析的支持。我们可以使用如Protocol Buffers、Thrift、Avro或者MessagePack等序列化包来方便的处理保存于znode节点的数据格式,不过有些时候,以UTF-8或ASCII编码的字符串已经够用了。
zookeeper的API暴露了以下方法:
create /path data 创建一个名为 /path 的znode节点,并包含数据data。
delete /path 删除名为/path的znode
exists /path 检查是否存在名为/path的节点
setData /path data 设置名为/path的znode的数据为data
getData /path 返回名为/path节点的数据信息
getChildren /path 返回所有/path节点的所有子节点列表
需要注意的是,zookeeper并不允许局部写入或读取znode节点的数据。当设置一个znode节点的数据或读取时,znode节点的内容会被整个替换或全部读取进来。
zookeeper客户端连接到zookeeper服务,通过API调用来建立会话(session),至于如何在zookeeper的命令控制台输入命令行运行zookeeper指令,后面会介绍。
持久节点和临时节点
当新建znode时,还需要指定该节点的类型(mode),不同的类型决定了znode节点的行为方式。
znode节点可以是持久(persistent)节点,还可以是临时(ephemeral)节点。持久的znode,如/path,只能通过调用delete来进行删除,临时的znode与之相反,当创建该节点的客户端崩溃或者关闭了与zookeeper的连接时,这个节点就会被删除。
持久znode是一种非常有用的znode,可以通过持久类型的znode为应用保存一些数据,即使znode的创建者不再属于应用系统时,数据也可以保存下来而不丢失。例如,在主从模式例子中,需要保存从节点的任务分配情况,即使分配任务的主节点已经崩溃了。
临时znode传达了应用某些方面的信息,仅当创建者的会话有效时,这些信息必须有效保存。例如,在主从模式的例子中,当主节点创建的znode为临时节点时,该节点的存在意味着现在有一个主节点,且主节点状态处于正常运行状态。如果主znode消失后,该znode仍然存在,那么系统将无法监测到主节点的崩溃。这样就可以阻止系统继续进行,因此这个znode需要和主节点一起消失。我们也在从节点中使用临时的znode,如果一个从节点失效,那么会话将会过期,之后 znode /workers 也将自动消失。
一个临时节点,在以下两种情况会被自动删除:
1 当创建该znode的客户端的会话因超时或主动关闭而终止时
2 当某个客户端(不一定是创建者)主动删除该节点时
因为临时的znode在其创建者的会话过期时被删除,所以我们现在不允许临时节点拥有子节点。有一个想法是让子节点也都是临时节点,这个功能也许会出现在未来新发布的版本中。
有序节点
一个znode还可以设置为有序(sequential)节点。一个有序znode节点被分配唯一一个单调递增的整数。当创建有序节点时,一个序号会被追加到路径之后。例如,如果一个客户端创建了一个有序znode节点,其路径为/tasks/task-,那么zookeeper将会分配一个序号,例如1,并将这个数字追加到路径之后,最后改znode节点为/tasks/task-1。有序znode通过提供了创建具有唯一名称的znode的简单方式。同时也通过这种方式可以直观的查看znode的创建顺序。
总结下来,znode一共有四种节点,持久节点,临时节点,持久有序节点,临时有序节点。
监视与通知
zookeeper通常以远程服务的方式被访问,如果每次访问znode时,客户端都需要获得节点中的内容,这样的代价就非常大。因为这样会导致更高的延迟。而且zookeeper需要做更多的操作。如下图中的例子,第二次调用 getChildren /task 返回了相同的空值,其实是没有必要的。
这是一个常见的轮询问题,为了替换客户端的轮询,我们应该选择基于通知(notification)的机制:客户端向zookeeper注册需要接受通知的znode,通过对znode设置监视点(watch)来接收通知,监视点是一个单次触发的操作,就是说监视点会触发一个通知。为了接收多个通知,客户端必须在每次通知后设置一个新的监视点,在下图的情况下,当节点/task发生变化时,客户端会受到一个通知,并从zookeeper读取一个新的值。
当使用通知机制时,还有一些需要知道的事情,因为通知机制是单次触发的操作,所以在客户端接收一个znode变更通知并设置新的监视点时,znode节点也许会发生新的变化(不用担心,这个不会错过)。下面看一个例子看它是如何工作的,假设事件发生顺序为:
1 客户端1设置监视点来监控task数据的变化
2 客户端1连接后,向task添加了一个新任务
3 客户端1 接收通知
4 客户端1设置新的监视点,在设置完成前,第三个客户端3连接后,向task中添加了一个新任务
客户端1最终设置了新的监视点,但是客户端3添加数据的变更并没有触发一个通知。为了观察这个变更,在设置新的监视点前,客户端1实际上需要读取节点task的状态,通过在设置监视点前读取zookeeper的状态,最终客户端1就不会错过任何变更。
通知机制的一个重要保障是,对同一个znode操作,先向客户端传送通知,然后再对该节点进行变更,如果客户端对同一个znode设置了监视点,而该znode发生了两个连续更新,第一次更新后,客户端在观察第二次变化前就接收到了通知,然后读取znode中的数据,我们认为主要特性在于通知机制阻止了客户端所观察的更新顺序。虽然zookeeper状态变化传播给某些客户端时变慢,但是我们保障了客户端以全局的顺序来观察zookeeper的状态。
zookeeper可以定义不同类型的通知,这个依赖于设置监视点对应的通知类型,客户端可以设置多种监视点,如监控znode的数据变化,监控znode子节点的变化,监控znode的创建或删除,为了设置监视点,可以使用任何api中的调用来读取zookeeper的状态,在调用这些api时,传入一个watcher或者使用默认的watcher。
注意,谁来管理我的缓存?如果不让客户端来管理其拥有的zookeeper数据的缓存,我们不得不让zookeeper来管理这些应用程序的缓存,但是,这样会导致zookeeper的设计更加复杂,事实上,如果zookeeper管理缓存失败,可能会导致zookeeper在运行时,停滞在等待客户端确认一个缓存失效的请求上,因为进行所有的写操作前,需要确认所有的缓存数据是否已经失效。
节点版本
每一个znode都有一个版本号,它随着每次数据变化而自增。两个api操作可以有条件的执行:setData和delete。这两个调用以版本号作为传入参数,只有当传入的参数的版本号与服务器上的版本号一致时,调用才会成功。当多个zookeeper客户端对同一个znode进行操作时,版本的使用就会显得尤为重要。例如,假设客户端1对znode的config写入了一些配置信息,如果另一个客户端2同时更新了这个znode,此时客户端1的版本号已经过期,客户端1调用setData一定不会成功。使用版本机制有效的避免了以上情况。这个例子中,客户端1在写入数据时使用的版本无法匹配,使得操作失败,如下图:
我们的交流基地,“JAVA互联网技术交流:789650498”欢迎小伙伴们一起来交流: