blog.leanote.com/post/holynull/Zookeeper

Zookeeper是Hadoop分布式调度服务,用于构建分布式应用系统。构建分布式应用程序是一件非常复杂的事情,主要原因是我们需要合理有效地处理分布式集群中的一些故障。例如,当集群中的节点相互通信时,节点A向节点B发送消息..如果一个节点想知道消息是否发送成功,只能由b节点告知a节点。那么如果节点B关机或者因为其他原因离开集群网络,问题就出现了。a节点一直给b发送消息,得不到b的响应。b没有办法通知节点a离线或关机。集群中的其他节点根本不知道B发生了什么,一直给B发送消息,此时你的整个集群出现了部分故障。

Zookeeper不能让一些故障完全消失,但是它提供了一些工具,可以让你的分布式应用安全合理的处理一些故障。

安装并运行动物园管理员

我们使用独立模式来安装和运行一个单独的动物园管理员服务。请在安装之前确认您已经安装了Java运行时环境。

我们转到Apache ZooKeeper发布页面下载ZooKeeper安装包,并在本地解压缩:

% tar xzf zookeeper-x.y.z.tar.gz

ZooKeeper提供了一些执行程序的工具。为了方便起见,我们将这些工具的路径添加到PATH环境变量中:

% export Zookeeper _ HOME = ~/SW/Zookeeper-x . y . z

% export PATH = $ PATH:$ Zookeeper _ HOME/bin

我们需要在运行ZooKeeper之前写一个配置文件。通常,配置文件位于安装目录下的conf/zoo.cfg中。我们可以把这个文件放在/etc/zookeer或者其他目录下,在环境变量中设置ZOOCFGDIR指向这个目录。以下是配置文件的内容:

tickTime=2000

数据目录=/用户/汤姆/动物园管理员

clientPort=2181

TickTime是zookeeper中的基本时间单位,单位是毫秒。Datadir是存储zookeeper持久数据的目录。ClientPort是zookeeper监听客户端连接的端口,默认值为2181。

开始命令:

% zkServer.sh启动

我们通过nc或telnet命令访问端口2181,执行ruok(你没事吧?)命令检查zookeeper是否启动成功:

% echo ruok | nc localhost 2181

爱摸客

然后看到动物园管理员回答我们“我没事”。下表显示了所有动物园管理员的姓名,由4个字符组成。

3.5.0以上版本会有嵌入式web服务,以上命令列表可以通过访问http://localhost:8080/commands访问。

动物园管理员开发示例

在这一节中,我们将解释如何编写zookeeper客户端程序来控制Zookeeper上的数据,从而管理客户端集群的成员资格。

动物园管理员中的组和成员

我们可以把Zookeeper理解为一个高度可用的文件系统。但是它没有文件和文件夹的概念,只有一个节点概念叫znode。那么znode就是数据和其他节点的容器。(其实znode可以理解为一个文件或者一个文件夹。)我们用父节点与子节点的关系来表示组和成员的关系。那么一个节点代表一个组,组节点下的子节点代表组中的成员。如下图所示:

创建组

我们使用动物园管理员的Java API创建了一个/zoo的组节点:

公共类CreateGroup实现Watcher {

私有静态最终int SESSION _超时= 5000;

私人动物园管理员ZK;

专用计数下拉列表连接信号=新计数下拉列表(1);

公共空连接(字符串主机)引发IOException,中断异常{

zk = new ZooKeeper(hosts,SESSION_TIMEOUT,this);

connected signal . await();

}

@覆盖

public void process(WatchedEvent事件){ // Watcher接口

if(event . GetState()= = Keeperstate。SyncConnected) {

connectedSignal .倒计时();

}

}

公共无效创建(字符串组名)引发KeeperException,

中断异常{

字符串路径= "/"+GroupName;

string CreatedPath = ZK . create(path,null/*data*/,Ids。OPEN_ACL_UNSAFE,

CreateMode。PERSISTENT);

system . out . println(" Created "+createdPath);

}

public void close()引发中断异常{

ZK . close();

}

公共静态void main(String[] args)引发异常{

create GrouP create GrouP = new create GrouP();

create GrouP . connect(args[0]);

create GrouP . create(args[1]);

create GrouP . close();

}

}

在执行main()时,先创建一个CreateGroup的对象,然后调用connect()方法通过zookeeper API连接zookeer服务器。要创建连接,我们需要三个参数:第一,服务器端主机名和端口号;第二,客户端连接到服务器会话的超时时间;第三,观察者界面的实例。观察者实例负责接收当动物园管理员数据改变时产生的事件回调。

在连接函数中创建一个zookeeper的实例,然后建立与服务器的连接。建立连接函数将立即返回,因此我们需要等待连接建立成功后再进行其他操作。我们使用CountDownLatch来阻塞当前线程,直到zookeeper准备好。这时,我们看到了守望者这个角色。我们实现了一个观察者界面的方法:

公共作废流程(WatchedEvent事件);

当客户端连接到动物园管理员服务器时,观察者将通过process()函数接收到一个成功连接的事件。接下来,我们调用CountDownLatch来释放前一个块。

连接成功后,我们调用create()方法。在这个方法中,我们调用zookeeper实例的create()方法来创建一个znode。参数包括:第一,路径;znode第二,znode(一个二进制数组)的内容,第三,一个访问控制列表(ACL),最后,znode的属性。

znode的属性可以分为短暂性和持久性。当创建客户端的会话结束或客户端由于其他原因与服务器断开连接时,临时节点会自动删除。但是,除非客户端主动删除,否则不会自动删除持久性znode,并且创建它的客户端可能不会删除它,其他客户端也会删除它。这里我们创建一个持久的znode。

Create()将返回znode的路径。让我们打印出新znode的路径。

我们执行上述程序:

% export CAPATH = ch21-ZK/target/CLaSS/:$ Zookeeper _ HOME/*:

$ Zookeeper _ HOME/lib/*:$ Zookeeper _ HOME/conf

% java CreateGroup localhost zoo

已创建/动物园

加入组

接下来,我们实现如何在一个组中注册成员。我们将使用短暂的znode来创建这些成员节点。当客户端程序退出时,这些成员将被删除。

我们创建一个连接观察器类,然后继承并实现一个连接组类:

公共类ConnectionWatcher实现Watcher {

私有静态最终int SESSION _超时= 5000;

受保护的动物园管理员ZK;

专用计数下拉列表连接信号=新计数下拉列表(1);

公共空连接(字符串主机)引发IOException,中断异常{

zk = new ZooKeeper(hosts,SESSION_TIMEOUT,this);

connected signal . await();

}

@覆盖

公共void流程(WatchedEvent事件){

if(event . GetState()= = Keeperstate。SyncConnected) {

connectedSignal .倒计时();

}

}

public void close()引发中断异常{

ZK . close();

}

}

公共类JoinGroup扩展了ConnectionWatcher {

公共无效连接(字符串组名,字符串成员名)引发KeeperException,

中断异常{

字符串路径= "/+group name+"/+MemberName;

string CreatedPath = ZK . create(path,null/*data*/,Ids。OPEN_ACL_UNSAFE,

CreateMode。短命);

system . out . println(" Created "+createdPath);

}

公共静态void main(String[] args)引发异常{

JoinGrouP JoinGrouP = new JoinGrouP();

joinGrouP . connect(args[0]);

joinGroup.join(args[1],args[2]);

//保持活动状态,直到进程被终止或线程被中断

线程.睡眠(龙。MAX _ VALUE);

}

}

加入一个团体和创建一个团体非常相似。在我们添加了一个短暂的znode之后,线程被阻塞了。然后我们可以使用命令行来查看我们在zookeeper中创建的znode。当我们强行关闭被屏蔽的程序时,会发现我们创建的znode会自动消失。

成员列表

接下来,我们实现一个程序来列出一个组的所有成员。

公共类列表组扩展了ConnectionWatcher {

公共无效列表(字符串组名)引发KeeperException,

中断异常{

字符串路径= "/"+GroupName;

尝试{

列表<。字符串>children = zk.getChildren(path,false);

if (children.isEmpty()) {

system . out . printf(" group % sn中没有成员",group name);

system . exit(1);

}

for (String child : children) {

System.out.println(子级);

}

} catch (KeeperException。不例外e) {

System.out.printf("组%s不存在",GroupName);

system . exit(1);

}

}

公共静态void main(字符串[)参数)引发异常{

list group list group = new list group();

listGrouP . connect(args[0]);

list GrouP . list(args[1]);

list group . close();

}

}

在list()方法中,我们调用getChildren()方法获取某个路径下的子节点,然后打印出来。我们将尽力捕捉keeperexception。此处为nonodeexception,当znode不存在时将引发该异常。当我们运行该程序时,我们将看到以下结果,表明我们尚未向动物园组添加任何成员:

% java ListGroup localhost zoo

动物园里没有成员

我们可以运行前面的加入组来添加成员。后台运行一些JoinGroup程序,添加节点后处于睡眠状态:

% Java JoinGrouP localhost zoo duck & amp;

% Java JoinGrouP localhost zoo cow & amp;

% java JoinGroup localhost动物园山羊& amp

%山羊_pid=$!

最后一个命令是记录最后一个启动的java程序的pid,这样我们就可以在zoo下面列出成员后终止进程。

下面我们将打印出动物园下的成员:

% java ListGroup localhost zoo

山羊

奶牛

然后,我们将终止最后启动的JoinGroup客户端:

% kill $山羊_pid

几秒钟后,我们发现山羊淋巴结消失了。因为我们之前创建的山羊节点是一个临时节点,在ZooKeeper上创建这个节点的客户端的会话已经终止,因为这个回复在5秒后过期(我们将会话的超时时间设置为5秒):

% java ListGroup localhost zoo

奶牛

让我们回头看看我们做了什么。我们首先创建一个节点组,这些节点的创建者都在同一个分布式系统中。这些节点的创建者互不认识。一个创建者想要使用这些节点数据来做一些工作,比如通过znode节点是否存在来判断节点的创建者是否存在。

最后,我们不能完全解决仅通过组成员关系与节点通信时的网络错误。与群集组成员节点通信时,出现通信故障。我们需要重试或尝试与组中的其他节点通信,以解决此通信故障。

动物园管理员命令行工具

Zookeeper有一套命令行工具。我们可以如下使用它来查找zoo下的成员节点:

% zkCLi . sh-服务器localhost ls /zoo

[牛,鸭]

您可以在没有参数的情况下运行此工具来获取帮助。

删除分组

让我们来看看如何删除一个组。

ZooKeeper API提供了一个delete()方法来删除znode。我们通过输入znode的路径和版本号来删除我们想要删除的znode。除了使用路径来定位我们想要删除的znode,我们还需要一个参数,即版本号。ZooKeeper只允许我们在指定要删除的版本号时删除znode,这与znode的当前版本号是一致的。这是一种最优锁定机制,用于处理znode的读写冲突。我们也可以通过将版本号指定为-1来忽略版本号一致性检查。

在删除znode之前,我们需要删除它的子节点,如下面的代码所示:

公共类DeleteGroup扩展了ConnectionWatcher {

公共无效删除(字符串组名)引发KeeperException,

中断异常{

字符串路径= "/"+GroupName;

尝试{

列表<。字符串>children = zk.getChildren(path,false);

for (String child : children) {

zk.delete(path + "/" + child,-1);

}

zk.delete(path,-1);

} catch (KeeperException。不例外e) {

System.out.printf("组%s不存在",GroupName);

system . exit(1);

}

}

公共静态void main(String[] args)引发异常{

delete GrouP delete GrouP = new delete GrouP();

delete GrouP . connect(args[0]);

delete GrouP . delete(args[1]);

delete GrouP . close();

}

}

最后,我们执行以下操作来删除动物园组:

% java DeleteGroup localhost zoo

% java ListGroup localhost zoo

集体动物园不存在

动物园管理员服务

ZooKeeper是一个高度可用和高性能的调度服务。在这一节中,我们将描述他的模型、操作和界面。

数据模型数据模型

ZooKeeper包含一个树形数据模型,我们称之为znode。znode包含存储的数据和访问控制列表。ZooKeeper的设计是存储少量数据,而不是大量数据,所以znode的最大存储限制不超过1M。

数据访问被定义为原子的。什么是原子性?当一个客户端访问一个znode时,它不仅会得到部分数据;客户端访问数据以获取所有数据,或者读取失败而一无所获。同样的,写的时候要么写完所有的数据,要么写失败什么都写不出来。ZooKeeper可以保证写操作只有两个结果,成功和失败。永远不会出现只写一部分数据的情况。与HDFS不同,动物园管理员不支持字符的追加操作。原因是,HDFS是一个大数据存储,旨在支持流式数据访问,而动物园管理员不是。

我们可以通过路径来定位znode,就像Unix系统定位文件一样,用斜杠来表示路径。但是,znode的路径只能使用绝对路径,不能像Unix系统那样使用相对路径,即Zookeeper无法识别这样的路径../和。/.

节点的名称由Unicode字符组成,除zookeeper外,我们可以任意命名节点。为什么不能用zookeeper来命名一个节点?ZooKeeper默认命名了一个根节点来存储一些管理数据。

请注意,这里的路径不是URIs,而是Java API中字符串类型的变量。

短暂的节点

我们已经知道znode有两种类型:短暂的和持久的。创建znode时,我们指定znode的类型,以后不会修改。当创建znode的客户端会话结束时,临时类型的znode将被删除。持久型znode创建后,与客户端接触很少,除非主动删除,否则一直存在。短暂的znode没有任何子节点。

虽然短暂的znode没有绑定到客户端的会话,但是任何客户端都可以访问它,当然,这是在他们的ACL策略下。当我们创建一个分布式系统时,我们需要知道分布式资源是否可用。短命的znode就是为这种场景而产生的。和我们前面的例子一样,短命znode用于实现一个成员管理,任何客户端进程都可以随时知道其他成员是否可用。

Znode的序列号

如果我们在创建znode时使用排序标志,ZooKeeper会在我们指定的znode名称后添加一个数字。随着我们继续添加同名的znode,这个数字会不断增加。该序列号的计数器由这些排序节点的父节点维护。

如果我们请求创建一个名为/a/b-的znode,ZooKeeper会为我们创建一个名为/a/b-3的znode。我们请求创建一个名为/a/b-的znode,ZooKeeper将为我们创建一个名为/a/b-5的znode。动物园管理员分配给我们的序列号在不断增加。Java API中create()的结果就是znode的实际名称。

那么序列号是用来干什么的呢?当然是用来排序的!在下面的“A Lock Service”中,我们将讨论如何使用znode的序列号来构建共享锁。

观察模式手表

观察模式使客户端能够在某个节点发生变化时得到通知。在观察模式下,ZooKeeper服务的一些操作由其他操作启动和触发。例如,客户端对znode执行exist操作以确定目标znode是否存在,同时在znode上启动观察模式。如果znode不存在,这个存在将返回false。如果后来另一个客户端创建了这个znode,会触发观察模式,之前启动观察模式的客户端会被通知znode的创建事件。稍后我们将详细介绍其他操作和触发器。

观察模式只能触发一次。如果你想一直被告知znode的创建和删除,那么你需要不断启动znode上的观察模式。在上面的例子中,如果客户端仍然需要得到删除znode的通知,那么客户端在得到创建通知后仍然需要继续对这个znode进行exists操作,然后重新启动观察模式。

在配置服务中,有一个如何使用观察模式更新集群中的配置的示例。

运营运营

下表列出了9种动物园管理员操作。

在调用delete和setData操作时,我们必须指定一个znode的版本号,也就是说,我们必须指定要删除或更新哪个版本的znode数据。如果版本号不匹配,操作将失败。失败的原因可能是在我们提交之前,znode已经被修改过了,版本号也发生了递增的变化。那么我们该怎么办呢?我可以考虑再试一次或者调用其他操作。比如我们提交更新失败后,可以再次获取znode的当前数据,看看当前版本号是多少,然后再做更新操作。

zoookeeper可以看作是一个文件系统,但是因为zoookeeper文件非常小,所以它不提供一般文件系统所提供的打开、关闭或查找操作。

批量更新多次更新

ZooKeeper支持将一些原始操作合并成一个操作单元,然后执行这些操作。那么这个批量操作也是原子的,执行结果只有成功和失败两种。对于批量操作单元中的操作,不会出现某些操作执行成功,某些操作失败的情况,即要么全部成功,要么全部失败。

Multiupdate对于绑定一些结构化的全局变量很有用。例如,绑定无向图。无向图的顶点用znode表示。添加和删除边的操作是通过修改边的两个相关联的氧化锌DEs来实现的。如果我们用ZooKeeper的原始操作来实现边缘操作,那么znode的修改可能会出现两个不一致的地方(一个成功,一个失败)。然后,如果我们将修改两个氧化锌DEs的操作放入一个多重修改单元,我们可以确保两个氧化锌DEs都被成功修改或失败。这样就可以避免在修改无向图的边时出现不一致的修改。

蜜蜂

ZooKeeper客户端使用的核心编程语言是JAVA和c;也支持Perl、Python、REST。执行操作的方式分为同步执行和异步执行。我们以前在同步的Java API中见过这个存在。

公共状态存在(字符串路径,观察器)抛出KeeperException,

中断异常

以下代码以异步模式存在:

公共空间存在(字符串路径、观察器、统计回调cb、对象ctx)

在Java API中,异步方法的返回类型是void,操作的返回结果会传递给回调对象的回调函数。回调对象将在StatCallback接口中实现回调函数,以接收操作返回的结果。功能界面如下:

公共void processResult(int rc,String path,Object ctx,Stat Stat);

参数rc代表返回代码,请参考KeeperException中的定义。当stat参数为null时,非0的值表示异常。Parameters path和ctx等于客户端调用的exists方法中的参数,这两个参数通常用于确定回调中获得的响应来自哪个请求。参数ctx可以是任何对象,只有当路径参数不能消除请求的模糊性时,才会使用它。如果不需要参数ctx,可以将其设置为null。

观察模式触发观察触发器

读取操作,比如exists、getChildren、getData,会在znode上打开观察模式,写入操作会触发观察模式事件,比如create、delete、setData。访问控制列表操作不会启动观察模式。触发观察模式时,会生成一个事件,该事件的类型取决于触发它的操作:

exists发起的观察模式是通过创建、删除、更新znode的操作触发的。

getData启动的观察模式由删除znode和更新znode操作触发。znode的创建不会触发,因为getData操作成功的前提是znode必须已经存在。

getChildren启动的观察模式只有在其子节点被创建和删除,或者该节点被删除时才会被触发。我们可以通过事件类型来判断是删除节点还是删除子节点:NodeChildrenChanged表示删除子节点,NodeDeleted表示删除节点。

该事件包含触发该事件的znode的路径,因此我们可以通过NodeCreated和NodeDeleted事件知道哪个znode被创建或删除。如果我们需要知道在NodeChildrenChanged事件之后哪个子节点被改变了,我们需要再次调用getChildren来获得一个新的子节点列表。同样,在NodeDataChanged事件之后,我们需要调用getData来获取新数据。我们写程序的时候,会在收到事件通知后改变znode的状态,所以一定要清楚的记住znode的状态变化。

访问控制操作

在创建znode时,我们会给他一个ACL(访问控制列表)来决定谁可以对znode做什么。

ZooKeeper通过认证获取客户端的身份,然后通过ACL控制客户端的访问。认证方法如下:

消化

使用用户名和密码

简单认证和安全层

使用Kerberos身份验证

互联网协议(Internet Protocol的缩写)

使用客户端的IP进行身份验证

客户端可以在与ZooKeeper建立会话连接后授权自己。授权不是必须的,虽然znode的ACL要求客户端必须合法,但在这种情况下,客户端可以授权自己访问znode。在以下示例中,客户端使用用户名和密码进行自我授权:

zk.addAuthInfo("digest "," tom:secret "。getBytes());

ACL由认证模式、认证模式标识和一组权限组成。例如,我们想通过一个ip地址为10.0.0.1的客户端访问一个znode。然后,我们需要为znode设置一个ACL。认证方式采用IP认证方式。认证方式的ID为10.0.0.1,只允许读取权限。使用JAVA,我们将创建一个ACL对象,如下所示:

新的ACL(Perms。READ,new Id("ip "," 10 . 0 . 0 . 1 ");

下表将列出所有权限。请注意,exists操作不受ACL控制,任何客户端都可以通过exists操作获取任意znode的状态,从而知道znode是否真的存在。

在动物园里。Ids类,ACL有一些预定义的变量,包括OPEN_ACL_UNSAFE。此设置表示所有权限都将授予客户端(ADMIN权限除外)。

此外,我们可以使用ZooKeeper认证的插件机制来集成第三方的认证系统。

觉得这篇文章对你有帮助?请与更多人分享

关注“进口新品”,看技术干货

1.《zookeeper安装 Zookeeper 快速入门(上)》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。

2.《zookeeper安装 Zookeeper 快速入门(上)》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。

3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/guoji/1584024.html