ZooKeeper分布式过程协同技术详解-第二章

文章目录

了解zookeeper

一、zookeeper基础

很多用于协作的原语常常在很多应用之间共享,因此,设计一个用于协作需求的服务的方法往往是提供原语列表,暴露出每个原语的实例化调用方法,并直接控制这些实例。比如,我们可以说分布式锁机制组成了一 个重要的原语,同时暴露出创建(create)、获取(acquire)和释放 (release)三个调用方法。

所谓原语,一般是指由若干条指令组成的程序段,用来实现某个特定功能,在执行过程中不可被中断。

这种设计存在一些重大的缺陷:

  • 首先,我们要么预先提出一份详尽的原语列表,要么提供API的扩展,以便引入新的原语;
  • 其次,以这种方式 实现原语的服务使得应用丧失了灵活性。

因此,在ZooKeeper中我们另辟蹊径。ZooKeeper并不直接暴露原语, 取而代之,它暴露了由一小部分调用方法组成的类似文件系统的API,以便允许应用实现自己的原语。

我们通常使用菜谱(recipes)来表示这些原语的实现。菜谱包括ZooKeeper操作和维护一个小型的数据节点,这些节点被称为znode,采用类似于文件系统的层级树状结构进行管理。

图2-1描述了一个znode树的结构,根节点包含4个子节点,其中三个子节点拥有下一 级节点,叶子节点存储了数据信息。

在这里插入图片描述
针对一个znode,没有数据常常表达了重要的信息。比如,在主-从模式的例子中,主节点的znode没有数据,表示当前还没有选举出主节点。而图2-1中涉及的一些其他znode节点在主-从模式的配置中非常有用:

  • ·/workers节点作为父节点,其下每个znode子节点保存了系统中一个可用从节点信息。如图2-1所示,有一个从节点(foot.com:2181)。
  • ·/tasks节点作为父节点,其下每个znode子节点保存了所有已经创建并等待从节点执行的任务的信息,主-从模式的应用的客户端在/tasks下添加一个znode子节点,用来表示一个新任务,并等待任务状态的znode节点。
  • ·/assign节点作为父节点,其下每个znode子节点保存了分配到某个从节点的一个任务信息,当主节点为某个从节点分配了一个任务,就会在/assign下增加一个子节点。

1、API概述

znode节点可能含有数据,也可能没有。如果一个znode节点包含任何数据,那么数据存储为字节数组(byte array)。字节数组的具体格式特定于每个应用的实现,ZooKeeper并不直接提供解析的支持。我们可以使用如 Protocol Buffers、Thrift、Avro或MessagePack等序列化(Serialization)包 来方便地处理保存于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节点的内容会被整个替 换或全部读取进来。

2、znode的不同类型

当新建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拥 有子节点的问题,其想法是使其子节点也均为临时节点。这个功能也许会 出现在未来的发布版本中,但现在还是不可用的。

有序节点

一个znode还可以设置为有序(sequential)节点。一个有序znode节点被分配唯一个单调递增的整数。当创建有序节点时,一个序号会被追加到路径之后。例如,如果一个客户端创建了一个有序znode节点,其路径 为/tasks/task-,那么ZooKeeper将会分配一个序号,如1,并将这个数字追加到路径之后,最后该znode节点为/tasks/task-1。有序znode通过提供了创建具有唯一名称的znode的简单方式。同时也通过这种方式可以直观地查看 znode的创建顺序。总之,znode一共有4种类型:

  • 持久的(persistent)
  • 临时的 (ephemeral)
  • 持久有序的(persistent_sequential)
  • 临时有序的 (ephemeral_sequential)。

3、监视和通知(因为是单次出发,可能遇到在下一次监视之前,znode可能发生了变化,通过在设置监视点前读取zookeeper状态保证不会错过任何变更)

ZooKeeper通常以远程服务的方式被访问,如果每次访问znode时,客户端都需要获得节点中的内容,这样的代价就非常大。因为这样会导致更高的延迟,而且ZooKeeper需要做更多的操作。

考虑图2-2中的例子,第二次调用getChildren/tasks返回了相同的值,一个空的集合,其实是没有必要的。
在这里插入图片描述
这是一个常见的轮询问题。为了替换客户端的轮询,我们选择了基于通知(notification)的机制:

  • 客户端向ZooKeeper注册需要接收通知的 znode,通过对znode设置监视点(watch)来接收通知。
  • 监视点是一个单次触发的操作,意即监视点会触发一个通知。为了接收多个通知,客户端必须在每次通知后设置一个新的监视点。在图2-3阐述的情况下,当节点/tasks发生变化时,客户端会收到一个通知,并从ZooKeeper读取一个新值。

监视点是一个单次触发的操作,意即监视点会触发一个通知。为了接收多个通知,客户端必须在每次通知后设置一个新的监视点。

在这里插入图片描述
当使用通知机制时,还有一些需要知道的事情。因为通知机制是单次触发的操作,所以在客户端接收一个znode变更通知并设置新的监视点时, znode节点也许发生了新的变化(不要担心,你不会错过状态的变化)。让我们看一个例子来说明它到底是怎么工作的。假设事件按以下顺序发生:

  • 1.客户端c1设置监视点来监控/tasks数据的变化。
  • 2.客户端c1连接后,向/tasks中添加了一个新的任务。
  • 3.客户端c1接收通知。
  • 4.客户端c1设置新的监视点,在设置完成前,第三个客户端c3连接后, 向/tasks中添加了一个新的任务。

客户端c1最终设置了新的监视点,但由c3添加数据的变更并没有触发一个通知。为了观察这个变更,在设置新的监视点前,c1实际上需要读取节点/tasks的状态,通过在设置监视点前读取ZooKeeper的状态,最终,c1 就不会错过任何变更。

通知机制(先通知再更新,从而保证通知顺序不出现错误)

通知机制的一个重要保障是,对同一个znode的操作,先向客户端传送通知,然后再对该节点进行变更(通过这样来保证顺序,使得不要出现连续两次通知,因为可能出现通知顺序错误)

如果客户端对一个znode设置了监视点, 而该znode发生了两个连续更新。第一次更新后,客户端在观察第二次变化 前就接收到了通知,然后读取znode中的数据。我们认为主要特性在于通知 机制阻止了客户端所观察的更新顺序。虽然ZooKeeper的状态变化传播给某些客户端时更慢,但我们保障客户端以全局的顺序来观察ZooKeeper的状态。

通知类型(使得客户端可以监视不同的变化)

客户端可以设置多种监视点,如

  • 监控znode的数据变化
  • 监控znode 子节点的变化
  • 监控znode的创建或删除。

为了设置监视点,可以使用任何 API中的调用来读取ZooKeeper的状态,在调用这些API时,传入一个 watcher对象或使用默认的watcher。本章后续(主从模式的实现)及第4章 会以主从模式的例子来展开讨论,我们将深入研究如何使用该机制。

缓存管理

如果不让客户端来管理其拥有的ZooKeeper数据的缓存,我们不得不 让ZooKeeper来管理这些应用程序的缓存。但是,这样会导致ZooKeeper的 设计更加复杂。事实上,如果让ZooKeeper管理缓存失效,可能会导致 ZooKeeper在运行时,停滞在等待客户端确认一个缓存失效的请求上,因为在进行所有的写操作前,需要确认所有的缓存数据是否已经失效。

4、版本(每个znode都有一个版本号码,它随着每次数据变化而自增,setData、Delete会以版本号为参数,只有当参数的版本号和服务器上的版本号一致的时候调用才会成功)

每一个znode都有一个版本号,它随着每次数据变化而自增。两个API 操作可以有条件地执行:setData和delete。这两个调用以版本号作为转入参 数,只有当转入参数的版本号与服务器上的版本号一致时调用才会成功。 当多个ZooKeeper客户端对同一个znode进行操作时,版本的使用就会显得 尤为重要。

例如,假设客户端c1对znode/config写入了一些配置信息,如果 另一个客户端c2同时更新了这个znode,此时c1的版本号已经过期,c1调用 setData一定不会成功。使用版本机制有效避免了以上情况。在这个例子 中,c1在写入数据时使用的版本无法匹配,使得操作失败,图2-4描述了这 个情况。

在这里插入图片描述

二、ZooKeeper架构

应用通过客户端库来对ZooKeeper实现了调 用。客户端库负责与ZooKeeper服务器端进行交互。

图2-5展示了客户端与服务器端之间的关系。每一个客户端导入客户端 库,之后便可以与任何ZooKeeper的节点进行通信。

在这里插入图片描述

ZooKeeper服务器端运行于两种模式下:独立模式(standalone)和仲裁模式(quorum)。独立模式几乎与其术语所描述的一样:有一个单独的 服务器,ZooKeeper状态无法复制。在仲裁模式下,具有一组ZooKeeper服 务器,我们称为ZooKeeper集合(ZooKeeper ensemble),它们之前可以进行状态的复制,并同时为服务于客户端的请求。从这个角度出发,我们使 用术语“ZooKeeper集合”来表示一个服务器设施,这一设施可以由独立模式 的一个服务器组成,也可以仲裁模式下的多个服务器组成。

Zookeeper仲裁(保存数据的人数达到法定人数,ZooKeeper才能有效运行)

在仲裁模式下,ZooKeeper复制集群中的所有服务器的数据树。但如果让一个客户端等待每个服务器完成数据保存后再继续,延迟问题将无法接 受

在公共管理领域,法定人数是指进行一项投票所需的立法者的最小数量。而在ZooKeeper中,则是指为了使ZooKeeper工作必须有效运行的服务器的最小数量。这个数字也是服务器告知客户端安全保存数据前,需要保存客户端数据的服务器的最小个数。例如,我们一共有5个ZooKeeper服务 器,但法定人数为3个,这样,只要任何3个服务器保存了数据,客户端就 可以继续,而其他两个服务器最终也将捕获到数据,并保存数据。

选择法定人数准确的大小是一个非常重要的事。法定人数的数量需要保证不管系统发生延迟或崩溃,服务主动确认的任何更新请求需要保持下 去,直到另一个请求代替它。

法定人数的大小必须至少为3,即集合中5个服务器的多数原则。通过使用多数方案,我们就可以容许f个服务器的崩溃,在这里,f为小于集合中服务器数量的一半。例如,如果有5个服务器,可以容许最多 f=2个崩溃。

会话

会话的概念非常重要,对ZooKeeper的运行也非常关键。客户端提交给 ZooKeeper的所有操作均关联在一个会话上。当一个会话因某种原因而中止 时,在这个会话期间创建的临时节点将会消失。

客户端初始连接到集合中某一个服务器或一个 独立的服务器。客户端通过TCP协议与服务器进行连接并通信,但当会话 无法与当前连接的服务器继续通信时,会话就可能转移到另一个服务器 上。ZooKeeper客户端库透明地转移一个会话到不同的服务器。

会话提供了顺序保障,这就意味着同一个会话中的请求会以FIFO(先 进先出)顺序执行。

三、开始使用ZooKeeper

下载链接

  • bin目录中有启动 ZooKeeper的脚本。以.sh结尾的脚本运行于UNIX平台
  • 在conf目录中保存配置文件
  • lib目录包括Java的JAR文件,它们是运行ZooKeeper所需要的第三方文件。

1、第一个ZooKeeper会话

使用ZooKeeper发行包中bin/目录下的zkServer和zkCli工具。

  • 变更目录即cd到项目根目录下,重命名配置文件:
    mv conf/zoo_sample.cfg conf/zoo.cfg
  • 虽然是可选的,最好还是把data目录移出/tmp目录,以防止ZooKeeper 填满了根分区(root partition)。可以在zoo.cfg文件中修改这个目录的位 置。
    dataDir=/users/me/zookeeper
  • 启动服务端
    bin/zkServer.sh start
    这个服务器命令使得ZooKeeper服务器在后台中运行。如果在前台中运 行以便查看服务器的输出,可以通过以下命令运行:
    bin/zkServer.sh start-foreground
  • 启动客户端
    bin/zkCli.sh
客户端建立会话过程

下面1-5对应于图片中的1-5

  • 1、客户端启动程序来建立一个会话。
  • 2、客户端尝试连接到localhost/127.0.0.1:2181。
  • 3、客户端连接成功,服务器开始初始化这个新会话。
  • 4、会话初始化成功完成。
  • 5、服务器向客户端发送一个SyncConnected事件。
    在这里插入图片描述
    在输出的结尾,我们看到会话建立的日志消息。第一处提到“Initiating client connection.”。消息本身说明到底发生了什么,而额外的重要细节说 明了客户端尝试连接到客户端发送的连接串localhost/127.0.0.1:2181中的 一个服务器。这个例子中,字符串只包含了localhost,因此指明了具体连 接的地址。之后我们看到关于SASL的消息,我们暂时忽略这个消息,随后 一个确认信息说明客户端与本地的ZooKeeper服务器建立了TCP连接。后面 的日志信息确认了会话的建立,并告诉我们会话ID为: 0x13b6fe376cd0000。最后客户端库通过SyncConncted事件通知了应用。应 用需要实现Watcher对象来处理这个事件。下一节将详细说明事件。
例子
  • ls /:列出根(root)下的所有的znode节点
  • 创建一个名为/workers的znode
    create /workers “”
  • 删除名为/workers的znode
    delete /workers
  • 关闭会话
    quit
  • ZooKeeper服务端关闭
    bin/zkServer.sh stop

2、会话的状态即生命周期

会话的生命周期(lifetime)是指会话从创建到结束的时期,无论会话正常关闭还是因超时而导致过期。

一个会话的主要可能状态大多是简单明了的:

  • CONNECTING
  • CONNECTED
  • CLOSED
  • NOT_CONNECTED。

状态的转换依赖于发生 在客户端与服务之间的各种事件(见图2-6)。

在这里插入图片描述

  • 一个会话从NOT_CONNECTED状态开始,当ZooKeeper客户端初始化后转换到CONNECTING状态(图2-6中的箭头1)。
  • 正常情况下,成功与 ZooKeeper服务器建立连接后,会话转换到CONNECTED状态(箭头2)。
  • 当客户端与ZooKeeper服务器断开连接或者无法收到服务器的响应时,它就会转换回CONNECTING状态(箭头3)并尝试发现其他ZooKeeper服务器。 如果可以发现另一个服务器或重连到原来的服务器,当服务器确认会话有效后,状态又会转换回CONNECTED状态。
  • 否则,它将会声明会话过期,然后转换到CLOSED状态(箭头4)。应用也可以显式地关闭会话(箭头4 和箭头5)。
发生网络分区时等待CONNECTING

如果一个客户端与服务器因超时而断开连接,客户端仍然保持 CONNECTING状态。如果因网络分区问题导致客户端与ZooKeeper集合被 隔离而发生连接断开,那么其状态将会一直保持,直到显式地关闭这个会 话,或者分区问题修复后,客户端能够获悉ZooKeeper服务器发送的会话 已经过期。发生这种行为是因为ZooKeeper集合对声明会话超时负责,而 不是客户端负责。直到客户端获悉ZooKeeper会话过期,否则客户端不能 声明自己的会话过期。然而,客户端可以选择关闭会话。

创建一个会话时,你需要设置会话超时这个重要的参数,这个参数设 置了ZooKeeper服务允许会话被声明为超时之前存在的时间。如果经过时间 t之后服务接收不到这个会话的任何消息,服务就会声明会话过期。而在客户端侧,如果经过t/3的时间未收到任何消息,客户端将向服务器发送心跳 消息。在经过2t/3时间后,ZooKeeper客户端开始寻找其他的服务器,而此 时它还有t/3时间去寻找。

客户端会尝试连接哪一个服务器?(当尝试连接到一个不同的服务器时,非常重要的是,这个服务器的 ZooKeeper状态要与最后连接的服务器的ZooKeeper状态保持最新。客户端不能连接到这样的服务器:它未发现更新而客户端却已经发现的更新。)

在仲裁模式下,客户端有多个服务器可以连接,而在独立模式下,客 户端只能尝试重新连接单个服务器。在仲裁模式中,应用需要传递可用的 服务器列表给客户端,告知客户端可以连接的服务器信息并选择一个进行连接。

当尝试连接到一个不同的服务器时,非常重要的是,这个服务器的 ZooKeeper状态要与最后连接的服务器的ZooKeeper状态保持最新。客户端不能连接到这样的服务器:它未发现更新而客户端却已经发现的更新。 ZooKeeper通过在服务中排序更新操作来决定状态是否最新。ZooKeeper确 保每一个变化相对于所有其他已执行的更新是完全有序的。因此,如果一 个客户端在位置i观察到一个更新,它就不能连接到只观察到i’<i的服务器 上。在ZooKeeper实现中,系统根据每一个更新建立的顺序来分配给事务标识符。

图2-7描述了在重连情况下事务标识符(zkid)的使用。当客户端因超 时与s1断开连接后,客户端开始尝试连接s2,但s2延迟于客户端所知的变 化。然而,s3对这个变化的情况与客户端保持一致,所以s3可以安全连接。

在这里插入图片描述

3、ZooKeeper与仲裁模式

到目前为止,我们一直基于独立模式配置的服务器端。如果服务器启动,服务就启动了,但如果服务器故障,整个服务也因此而关闭。这非常不符合可靠的协作服务的承诺。出于可靠性,我们需要运行多个服务器。

幸运的是,我们可以在一台机器上运行多个服务器。我们仅仅需要做 的便是配置一个更复杂的配置文件。

为了让服务器之间可以通信,服务器间需要一些联系信息。理论上, 服务器可以使用多播来发现彼此,但我们想让ZooKeeper集合支持跨多个网络而不是单个网络,这样就可以支持多个集合的情况。

为了完成这些,我们将要使用以下配置文件:
在这里插入图片描述
我们主要讨论最后三行对于server.n项的配置信息。其余配置参数将会 在第10章中进行说明。
每一个server.n项指定了编号为n的ZooKeeper服务器使用的地址和端口号。每个server.n项通过冒号分隔为三部分,第一部分为服务器n的IP地址或主机名(hostname),第二部分和第三部分为TCP端口号,分别用于仲裁通信和群首选举。因为我们在同一个机器上运行三个服务器进程,所以 我们需要在每一项中使用不同的端口号。通常,我们在不同的服务器上运 行每个服务器进程,因此每个服务器项的配置可以使用相同的端口号。

我们还需要分别设置data目录,我们可以在命令行中通过以下命令来操作:
在这里插入图片描述
当启动一个服务器时,我们需要知道启动的是哪个服务器。一个服务 器通过读取data目录下一个名为myid的文件来获取服务器ID信息。
可以通 过以下命令来创建这些文件:
在这里插入图片描述
当服务器启动时,服务器通过配置文件中的dataDir参数来查找data目录的配置。它通过mydata获得服务器ID,之后使用配置文件中server.n对应的项来设置端口并监听。当在不同的机器上运行ZooKeeper服务器进程时, 它们可以使用相同的客户端端口和相同的配置文件。但对于这个例子,在 一台服务器上运行,我们需要自定义每个服务器的客户端端口。

因此,首先使用本章之前讨论的配置文件,创建z1/z1.cfg。之后通过分别改变客户端端口号为2182和2183,创建配置文件z2/z2.cfg和z3/z3.cfg。

现在可以启动服务器,让我们从z1开始:

在这里插入图片描述
服务器的日志记录为zookeeper.out。因为我们只启动了三个ZooKeeper 服务器中的一个,所以整个服务还无法运行。在日志中我们将会看到以下 形式的记录:
在这里插入图片描述
这个服务器疯狂地尝试连接到其他服务器,然后失败,如果我们启动 另一个服务器,我们可以构成仲裁的法定人数:
在这里插入图片描述
如果我们观察第二个服务器的日志记录zookeeper.out,我们将会看 到:
在这里插入图片描述

该日志指出服务器2已经被选举为群首。如果我们现在看看服务器1的 日志,我们会看到:
在这里插入图片描述
服务器1作为服务器2的追随者被激活。我们现在具有了符合法定仲裁 (三分之二)的可用服务器。

在此刻服务开始可用。我们现在需要配置客户端来连接到服务上。连 接字符串需要列出所有组成服务的服务器host:port对。对于这个例子,连 接串为"127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183"(我们包含第 三个服务器的信息,即使我们永远不启动它,因为这可以说明ZooKeeper一 些有用的属性)。

我们使用zkCli.sh来访问集群:

在这里插入图片描述
当连接到服务器后,我们会看到以下形式的消息:
在这里插入图片描述
注意日志消息中的端口号,在本例中的2182。如果通过Ctrl-C来停止 客户端并重启多次它,我们将会看到端口号在218102182之间来回变化。我 们也许还会注意到尝试2183端口后连接失败的消息,之后为成功连接到某 一个服务器端口的消息。

简单的负载均衡

客户端以随机顺序连接到连接串中的服务器。这样可以用ZooKeeper 来实现一个简单的负载均衡。不过,客户端无法指定优先选择的服务器来 进行连接。例如,如果我们有5个ZooKeeper服务器的一个集合,其中3个 在美国西海岸,另外两个在美国东海岸,为了确保客户端只连接到本地服务器上,我们可以使在东海岸客户端的连接串中只出现东海岸的服务器, 在西海岸客户端的连接串中只有西海岸的服务器。

这个连接尝试说明如何通过运行多个服务器来达到可靠性(当然,在生产环境中,你需要在不同的主机上进行这些操作)。对于本书大部分, 包括后续几章,我们一直以独立模式的服务器进行开发,因为启动和管理 多个服务器非常简单,实现这个例子也非常简单。除了连接串外,客户端 不用关心ZooKeeper服务由多少个服务器组成,这也是ZooKeeper的优点之 一。

4、实现一个原语:通过ZooKeeper实现锁

关于ZooKeeper的功能,一个简单的例子就是通过锁来实现临界区域。我们知道有很多形式的锁(如:读/写锁、全局锁),通过ZooKeeper来实 现锁也有多种方式。

假设有一个应用由n个进程组成,这些进程尝试获取一个锁。再次强调,ZooKeeper并未直接暴露原语,因此我们使用ZooKeeper的接口来管理 znode,以此来实现锁。为了获得一个锁,每个进程p尝试创建znode,名 为/lock。如果进程p成功创建了znode,就表示它获得了锁并可以继续执行 其临界区域的代码。不过一个潜在的问题是进程p可能崩溃,导致这个锁永 远无法释放。在这种情况下,没有任何其他进程可以再次获得这个锁,整个系统可能因死锁而失灵。为了避免这种情况,我们不得不在创建这个节 点时指定/lock为临时节点。

其他进程因znode存在而创建/lock失败。因此,进程监听/lock的变化, 并在检测到/lock删除时再次尝试创建节点来获得锁。
当收到/lock删除的通 知时,如果进程p还需要继续获取锁,它就继续尝试创建/lock的步骤,如果 其他进程已经创建了,就继续监听节点。

四、一个主-从模式例子的实现

本节中我们通过zkCli工具来实现主-从示例的一些功能。这个例子仅用于教学目的,我们不推荐使用zkCli工具来搭建系统。使用zkCli的目的仅仅 是为了说明如何通过ZooKeeper来实现协作菜谱,从而撇开在实际实现中所需的大量细节。我们将在下一章中进入实现的细节。

主-从模式的模型中包括三个角色:

  • 主节点
    主节点负责监视新的从节点和任务,分配任务给可用的从节点。
  • 从节点
    从节点会通过系统注册自己,以确保主节点看到它们可以执行任务, 然后开始监视新任务。
  • 客户端
    客户端创建新任务并等待系统的响应。 现在探讨这些不同的角色以及每个角色需要执行的确切步骤。

下面这张图片是根据主从模式画出来的,每个节点可能还有数据。

在这里插入图片描述

1、主节点角色

因为只有一个进程会成为主节点,所以一个进程成为ZooKeeper的主节点后必须锁定管理权。为此,进程需要创建一个临时znode,名为/master:

创建临时节点create -e /master “”

在这里插入图片描述
上面图片中的1、2、3的意思分别为:

  • 创建主节点的znode,以便获得管理权。使用-e标志来表示创建的 znode为临时性的。
  • 列出ZooKeeper树的根。
  • 获取/master znode的元数据和数据。

刚刚发生了什么?首先创建一个临时znode/master。我们在znode中添加了主机信息,以便ZooKeeper外部的其他进程需要与它通信。添加主机信息并不是必需的,但这样做仅仅是为了说明我们可以在需要时添加数据。 为了设置znode为临时性的,需要添加-e标志。记得,一个临时节点会在会 话过期或关闭时自动被删除。

现在让我们看下我们使用两个进程来获得主节点角色的情况,尽管在任何时刻最多只能有一个活动的主节点,其他进程将成为备份主节点。假如其他进程不知道已经有一个主节点被选举出来,并尝试创建一个/master 节点。让我们看看会发生什么:
在这里插入图片描述

监视master节点stat /master true

ZooKeeper告诉我们一个/master节点已经存在。这样,第二个进程就知道已经存在一个主节点。然而,一个活动的主节点可能会崩溃,备份主节点需要接替活动主节点的角色。为了检测到这些,需要在/master节点上设置一个监视点,操作如下:
在这里插入图片描述
stat命令可以得到一个znode节点的属性,并允许我们在已经存在的 znode节点上设置监视点。通过在路径后面设置参数true来添加监视点。当 活动的主节点崩溃时,我们会观察到以下情况:

如果监视了/master之后,如果创建临时节点的会话关闭了,就会得到通知matser节点被删除。
在这里插入图片描述
因为备份主节点成功创建了/master节点,所以现在客户端开始成为活 动主节点。

2、从节点、任务和分配

在我们讨论从节点和客户端所采取的步骤之前,让我们先创建三个重要的父znode,/workers、/tasks和/assign:

在这里插入图片描述
这三个新的znode为持久性节点,且不包含任何数据。本例中,通过使用这些znode可以告诉我们哪个从节点当前有效,还告诉我们当前有任务需要分配,并向从节点分配任务。

ls /workers true也会监视/workers节点

在真实的应用中,这些znode可能由主进程在分配任务前创建,也可能由一个引导程序创建,不管这些节点是如何创建的,一旦这些节点存在了,主节点就需要监视/workers和/tasks的子节点的变化情况:

在这里插入图片描述
请注意,在主节点上调用stat命令前,我们使用可选的true参数调用ls命令。通过true这个参数,可以设置对应znode的子节点变化的监视点。

3、从节点角色

从节点首先要通知主节点,告知从节点可以执行任务。从节点通过 在/workers子节点下创建临时性的znode来进行通知,并在子节点中使用主 机名来标识自己:
在这里插入图片描述
注意,输出中,ZooKeeper确认znode已经创建。之前主节点已经监视了/workers的子节点变化情况。一旦从节点在/workers下创建了一个znode, 主节点就会观察到以下通知信息:
在这里插入图片描述
下一步,从节点需要创建一个父znode/assign/worker1.example.com来接收任务分配,并通过第二个参数为true的ls命令来监视这个节点的变化,以 便等待新的任务。
在这里插入图片描述
在这里插入图片描述
从节点现在已经准备就绪,可以接收任务分配。之后,我们通过讨论 客户端角色来看一下任务分配的问题。

4、客户端角色

客户端向系统中添加任务。在本示例中具体任务是什么并不重要,我 们假设客户端请求主从系统来运行cmd命令。为了向系统添加一个任务, 客户端执行以下操作:
在这里插入图片描述
我们需要按照任务添加的顺序来添加znode,其本质上为一个队列。客 户端现在必须等待任务执行完毕。执行任务的从节点将任务执行完毕后, 会创建一个znode来表示任务状态。客户端通过查看任务状态的znode是否 创建来确定任务是否执行完毕,因此客户端需要监视状态znode的创建事 件:
在这里插入图片描述
执行任务的从节点会在/tasks/task-0000000000节点下创建状态znode节点,所以我们需要用ls命令来监视/tasks/task-0000000000的子节点。

一旦创建任务的znode,主节点会观察到以下事件:
在这里插入图片描述
主节点之后会检查这个新的任务,获取可用的从节点列表,之后分配 这个任务给worker1.example.com:
在这里插入图片描述
从节点接收到新任务分配的通知:
在这里插入图片描述
从节点之后便开始检查新任务,并确认该任务是否分配给自己:
在这里插入图片描述
一旦从节点完成任务的执行,它就会在/tasks中添加一个状态znode:
在这里插入图片描述
之后,客户端接收到通知,并检查执行结果:
在这里插入图片描述
客户端检查状态znode的信息,并确认任务的执行结果。本例中,我们 看到任务成功执行,其状态为“done”。当然任务也可能非常复杂,甚至涉 及另一个分布式系统。最终不管是什么样的任务,执行任务的机制与通过 ZooKeeper来传递结果,本质上都是一样的。

五、小结

我们看到了 ZooKeeper通过其API提供的基本功能,还探讨了其架构中的一些重要概念,如通过仲裁理论进行复制。此时此刻,最重要的并不是了解ZooKeeper 的复制协议是如何工作的,最重要的是明白仲裁理论的概念,因为你在部 署ZooKeeper时需要指定服务器的数量。讨论的另一个重要概念便是会话。 会话的语义对ZooKeeper的保障非常关键,因为它们常常会涉及会话。

为了对如何使用ZooKeeper提供初步的了解,我们使用zkCli工具来访 问ZooKeeper服务器,并执行请求。我们展示了使用该工具在主-从模式例 子中的主要操作。当实现一个真实的ZooKeeper应用时,你不应该使用这个 工具;这个工具更多地用于调试和监控目的。你需要使用ZooKeeper提供的 某一语言套件。在下一章中,我们将使用Java来实现我们的例子。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值