深入ZooKeeper——独立模式实践、仲裁模式解析

开始使用Zookeeper

开始之前,需要下载zookeeper发行包。
我通过重新配置网络,然后通过yum install wget获取wget命令,使用wget http://....zookeeper-3.4.10.tar.gz下载发行包,再用tar -xvzf zookeeper-3.4.10.tar.gz解压。由于我对linux忘记了大半,所以花了2小时。
在这里插入图片描述
在发行包(distribution)的目录中,你会发现在bin目录中有启动ZooKeeper的脚本。以.sh结尾的脚本运行于UNIX平台(Linux、Mac OSX等),以.cmd结尾的脚本则用于Windows。
lib目录包括Java的JAR文件,它们是运行ZooKeeper所需要的第三方文件。稍后我们需要引用ZooKeeper解压缩的目录。我们以{PATH_TO_ZK}方式来引用该目录。

第一个ZooKeeper会话

首先我们以独立模式运行ZooKeeper并创建一个会话。(使用ZooKeeper发行包中bin/目录下的zkServer和zkCli工具)
假设你已经下载并解压了ZooKeeper发行包,进入shell,变更目录(cd)到项目根目录下,重命名配置文件:
在这里插入图片描述
虽然是可选的,最好还是把data目录移出/tmp目录,以防止ZooKeeper填满了根分区(root partition)。可以在zoo.cfg文件中修改这个目录的位置。
通过vi zoo.cfg:修改为dataDir=/users/me/zookeeper
在这里插入图片描述
最后,为了启动服务器,执行以下命令:
在这里插入图片描述
当然,这个服务器命令使得ZooKeeper服务器在后台中运行。如果在前台中运行以便查看服务器的输出,可以通过以下命令运行:
./zkServer.sh start-foreground
这个选项提供了大量详细信息的输出,以便允许查看服务器发生了什么。

现在我们准备启动客户端。在另一个shell中进入项目根目录,运行以下命令:
./zkCli.sh
在这里插入图片描述
为了更加了解ZooKeeper,让我们列出根(root)下的所有znode,然后创建一个znode。首先我们要确认此刻znode树为空,除了节点/zookeeper之外,该节点内标记了ZooKeeper服务所需的元数据树。
在这里插入图片描述
现在发生了什么?我们执行ls/后看到这里只有/zookeeper节点。现在我们创建一个名为/workers的znode,确保如下所示:
在这里插入图片描述

当创建/workers节点后,我们指定了一个空字符串(""),说明我
们此刻不希望在这个znode中保存数据。然而,该接口中的这个参数可
以使我们保存任何字符串到ZooKeeper的节点中。比如,可以替
换"“为"workers”。

为了完成这个练习,删除znode,然后退出:
在这里插入图片描述
观察到znode/workers已经被删除,并且会话现在也关闭。为了完成最后的清理,退出ZooKeeper服务器:
在这里插入图片描述

会话的状态和声明周期

会话的生命周期(lifetime)是指会话从创建到结束的时期,无论会话正常关闭还是因超时而导致过期。
为了讨论在会话中发生了什么,我们需要考虑会话可能的状态,以及可能导致会话状态改变的事件。
一个会话的主要可能状态大多是简单明了的:CONNECTING、CONNECTED、CLOSED和NOT_CONNECTED。
在这里插入图片描述
一个会话从NOT_CONNECTED状态开始,当ZooKeeper客户端初始化后转换到CONNECTING状态(图2-6中的箭头1)。正常情况下,成功与ZooKeeper服务器建立连接后,会话转换到CONNECTED状态(箭头2)。当客户端与ZooKeeper服务器断开连接或者无法收到服务器的响应时,它就会转换回CONNECTING状态(箭头3)并尝试发现其他ZooKeeper服务器。
如果可以发现另一个服务器或重连到原来的服务器,当服务器确认会话有效后,状态又会转换回CONNECTED状态。否则,它将会声明会话过期,然后转换到CLOSED状态(箭头4)。应用也可以显式地关闭会话(箭头4和箭头5)。

创建一个会话时,你需要设置会话超时这个重要的参数,这个参数设置了ZooKeeper服务允许会话被声明为超时之前存在的时间。如果经过时间t之后服务接收不到这个会话的任何消息,服务就会声明会话过期。而在客户端侧,如果经过t/3的时间未收到任何消息,客户端将向服务器发送心跳消息。在经过2t/3时间后,ZooKeeper客户端开始寻找其他的服务器,而此时它还有t/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的功能,一个简单的例子就是通过锁来实现临界区域。我们知道有很多形式的锁(如:读/写锁、全局锁),通过ZooKeeper来实现锁也有多种方式。
假设有一个应用由n个进程组成,这些进程尝试获取一个锁。再次强调,ZooKeeper并未直接暴露原语,因此我们使用ZooKeeper的接口来管理znode,以此来实现锁。

为了获得一个锁,每个进程p尝试创建znode,名为/lock。如果进程p成功创建了znode,就表示它获得了锁并可以继续执行其临界区域的代码。不过一个潜在的问题是进程p可能崩溃,导致这个锁永远无法释放为了避免这种情况,我们不得不在创建这个节点时指定/lock为临时节点

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

本节中我们通过zkCli工具来实现主-从示例的一些功能。这个例子仅用于教学目的,我们不推荐使用zkCli工具来搭建系统。
主-从模式的模型中包括三个角色:

  • 主节点
  • 从节点
  • 客户端

主节点角色
因为只有一个进程会成为主节点,所以一个进程成为ZooKeeper的主节点后必须锁定管理权。为此,进程需要创建一个临时znode,名为/master:
在这里插入图片描述
①创建主节点的znode,以便获得管理权。使用-e标志来表示创建的znode为临时性的。
②列出ZooKeeper树的根。
③获取/master znode的元数据和数据。

现在让我们看下我们使用两个进程来获得主节点角色的情况,尽管在任何时刻最多只能有一个活动的主节点,其他进程将成为备份主节点。
假如其他进程不知道已经有一个主节点被选举出来,并尝试创建一个/master节点。让我们看看会发生什么:
在这里插入图片描述
ZooKeeper告诉我们一个/master节点已经存在。这样,第二个进程就知道已经存在一个主节点。然而,一个活动的主节点可能会崩溃,备份主节点需要接替活动主节点的角色。
为了检测到这些,需要在/master节点上设置一个监视点,操作如下:
在这里插入图片描述在这里插入图片描述
stat命令可以得到一个znode节点的属性,并允许我们在已经存在的znode节点上设置监视点。通过在路径后面设置参数true来添加监视点。当活动的主节点崩溃时,我们会观察到以下情况:
在这里插入图片描述
在输出的最后,我们注意到NodeDeleted事件。这个事件指出活动主节点的会话已经关闭或过期。同时注意,/master节点已经不存在了。现在备份主节点通过再次创建/master节点来成为活动主节点。
在这里插入图片描述

从节点、任务和分配
在我们讨论从节点和客户端所采取的步骤之前,让我们先创建三个重要的父znode,/workers、/tasks和/assign:
在这里插入图片描述
这三个新的znode为持久性节点,且不包含任何数据。通过使用这些znode可以告诉我们哪个从节点当前有效,还告诉我们当前有任务需要分配,并向从节点分配任务。
在真实的应用中,这些znode可能由主进程在分配任务前创建,也可能由一个引导程序创建,不管这些节点是如何创建的,一旦这些节点存在了,主节点就需要监视/workers和/tasks的子节点的变化情况:
在这里插入图片描述

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

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

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

猜你喜欢

转载自blog.csdn.net/No_Game_No_Life_/article/details/84101172