《ZooKeeper:分布式过程协同技术详解》——2.4 一个主-从模式例子的实现

本节书摘来自华章计算机《ZooKeeper:分布式过程协同技术详解》一书中的第2章,第2.4节,作者:Flavio Junqueira, Benjamin Reed 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

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

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

  • 主节点
    主节点负责监视新的从节点和任务,分配任务给可用的从节点。
  • 从节点
    从节点会通过系统注册自己,以确保主节点看到它们可以执行任务,然后开始监视新任务。
  • 客户端
    客户端创建新任务并等待系统的响应。

现在探讨这些不同的角色以及每个角色需要执行的确切步骤。

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

[zk: localhost:2181(CONNECTED) 0] create -e /master "master1.example.com:2223" 
Created /master
[zk: localhost:2181(CONNECTED) 1] ls / 
[master, zookeeper]
[zk: localhost:2181(CONNECTED) 2] get /master 
"master1.example.com:2223"
cZxid = 0x67
ctime = Tue Dec 11 10:06:19 CET 2012
mZxid = 0x67
mtime = Tue Dec 11 10:06:19 CET 2012
pZxid = 0x67
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x13b891d4c9e0005
dataLength = 26
numChildren = 0
[zk: localhost:2181(CONNECTED) 3]
创建主节点的znode,以便获得管理权。使用-e标志来表示创建的znode为临时性的。
列出ZooKeeper树的根。
获取/master znode的元数据和数据。

刚刚发生了什么?首先创建一个临时znode /master。我们在znode中添加了主机信息,以便ZooKeeper外部的其他进程需要与它通信。添加主机信息并不是必需的,但这样做仅仅是为了说明我们可以在需要时添加数据。为了设置znode为临时性的,需要添加-e标志。记得,一个临时节点会在会话过期或关闭时自动被删除。
现在让我们看下我们使用两个进程来获得主节点角色的情况,尽管在任何时刻最多只能有一个活动的主节点,其他进程将成为备份主节点。假如其他进程不知道已经有一个主节点被选举出来,并尝试创建一个/master节点。让我们看看会发生什么:

[zk: localhost:2181(CONNECTED) 0] create -e /master "master2.example.com:2223"
Node already exists: /master
[zk: localhost:2181(CONNECTED) 1]

ZooKeeper告诉我们一个/master节点已经存在。这样,第二个进程就知道已经存在一个主节点。然而,一个活动的主节点可能会崩溃,备份主节点需要接替活动主节点的角色。为了检测到这些,需要在/master节点上设置一个监视点,操作如下:

[zk: localhost:2181(CONNECTED) 0] create -e /master "master2.example.com:2223"
Node already exists: /master
[zk: localhost:2181(CONNECTED) 1] stat /master true
cZxid = 0x67
ctime = Tue Dec 11 10:06:19 CET 2012
mZxid = 0x67
mtime = Tue Dec 11 10:06:19 CET 2012
pZxid = 0x67
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x13b891d4c9e0005
dataLength = 26
numChildren = 0
[zk: localhost:2181(CONNECTED) 2]

stat命令可以得到一个znode节点的属性,并允许我们在已经存在的znode节点上设置监视点。通过在路径后面设置参数true来添加监视点。当活动的主节点崩溃时,我们会观察到以下情况:

[zk: localhost:2181(CONNECTED) 0] create -e /master "master2.example.com:2223"
Node already exists: /master
[zk: localhost:2181(CONNECTED) 1] stat /master true
cZxid = 0x67
ctime = Tue Dec 11 10:06:19 CET 2012
mZxid = 0x67
mtime = Tue Dec 11 10:06:19 CET 2012
pZxid = 0x67
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x13b891d4c9e0005
dataLength = 26
numChildren = 0
[zk: localhost:2181(CONNECTED) 2]
WATCHER::

WatchedEvent state:SyncConnected type:NodeDeleted path:/master

[zk: localhost:2181(CONNECTED) 2] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 3]

在输出的最后,我们注意到NodeDeleted事件。这个事件指出活动主节点的会话已经关闭或过期。同时注意,/master节点已经不存在了。现在备份主节点通过再次创建
/master节点来成为活动主节点。

[zk: localhost:2181(CONNECTED) 0] create -e /master "master2.example.com:2223"
Node already exists: /master
[zk: localhost:2181(CONNECTED) 1] stat /master true
cZxid = 0x67
ctime = Tue Dec 11 10:06:19 CET 2012
mZxid = 0x67
mtime = Tue Dec 11 10:06:19 CET 2012
pZxid = 0x67
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x13b891d4c9e0005
dataLength = 26
numChildren = 0
[zk: localhost:2181(CONNECTED) 2]
WATCHER::

WatchedEvent state:SyncConnected type:NodeDeleted path:/master

[zk: localhost:2181(CONNECTED) 2] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 3] create -e /master "master2.example.com:2223"
Created /master
[zk: localhost:2181(CONNECTED) 4]

因为备份主节点成功创建了/master节点,所以现在客户端开始成为活动主节点。
2.4.2 从节点、任务和分配
在我们讨论从节点和客户端所采取的步骤之前,让我们先创建三个重要的父znode,

/workers、/tasks和/assign:
[zk: localhost:2181(CONNECTED) 0] create /workers ""
Created /workers
[zk: localhost:2181(CONNECTED) 1] create /tasks ""
Created /tasks
[zk: localhost:2181(CONNECTED) 2] create /assign ""
Created /assign
[zk: localhost:2181(CONNECTED) 3] ls /
[assign, tasks, workers, master, zookeeper]
[zk: localhost:2181(CONNECTED) 4]

这三个新的znode为持久性节点,且不包含任何数据。本例中,通过使用这些znode可以告诉我们哪个从节点当前有效,还告诉我们当前有任务需要分配,并向从节点分配任务。
在真实的应用中,这些znode可能由主进程在分配任务前创建,也可能由一个引导程序创建,不管这些节点是如何创建的,一旦这些节点存在了,主节点就需要监视
/workers和/tasks的子节点的变化情况:

[zk: localhost:2181(CONNECTED) 4] ls /workers true
[]
[zk: localhost:2181(CONNECTED) 5] ls /tasks true
[]
[zk: localhost:2181(CONNECTED) 6]

请注意,在主节点上调用stat命令前,我们使用可选的true参数调用ls命令。通过true这个参数,可以设置对应znode的子节点变化的监视点。
2.4.3 从节点角色
从节点首先要通知主节点,告知从节点可以执行任务。从节点通过在/workers子节点下创建临时性的znode来进行通知,并在子节点中使用主机名来标识自己:

[zk: localhost:2181(CONNECTED) 0] create -e /workers/worker1.example.com
                                            "worker1.example.com:2224"
Created /workers/worker1.example.com
[zk: localhost:2181(CONNECTED) 1]

注意,输出中,ZooKeeper确认znode已经创建。之前主节点已经监视了/workers的子节点变化情况。一旦从节点在/workers下创建了一个znode,主节点就会观察到以下通知信息:
WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/workers

下一步,从节点需要创建一个父znode/assing/worker1.example.com来接收任务分配,并通过第二个参数为true的ls命令来监视这个节点的变化,以便等待新的任务。

[zk: localhost:2181(CONNECTED) 0] create -e /workers/worker1.example.com
                                            "worker1.example.com:2224"
Created /workers/worker1.example.com
[zk: localhost:2181(CONNECTED) 1] create /assign/worker1.example.com ""
Created /assign/worker1.example.com
[zk: localhost:2181(CONNECTED) 2] ls /assign/worker1.example.com true
[]
[zk: localhost:2181(CONNECTED) 3]

从节点现在已经准备就绪,可以接收任务分配。之后,我们通过讨论客户端角色来看一下任务分配的问题。
2.4.4 客户端角色
客户端向系统中添加任务。在本示例中具体任务是什么并不重要,我们假设客户端请求主从系统来运行cmd命令。为了向系统添加一个任务,客户端执行以下操作:

[zk: localhost:2181(CONNECTED) 0] create -s /tasks/task- "cmd"
Created /tasks/task-0000000000

我们需要按照任务添加的顺序来添加znode,其本质上为一个队列。客户端现在必须等待任务执行完毕。执行任务的从节点将任务执行完毕后,会创建一个znode来表示任务状态。客户端通过查看任务状态的znode是否创建来确定任务是否执行完毕,因此客户端需要监视状态znode的创建事件:

[zk: localhost:2181(CONNECTED) 1] ls /tasks/task-0000000000 true
[]
[zk: localhost:2181(CONNECTED) 2]

执行任务的从节点会在/tasks/task-0000000000节点下创建状态znode节点,所以我们需要用ls命令来监视/tasks/task-0000000000的子节点。
一旦创建任务的znode,主节点会观察到以下事件:

[zk: localhost:2181(CONNECTED) 6]
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/tasks

主节点之后会检查这个新的任务,获取可用的从节点列表,之后分配这个任务给worker1.example.com:

[zk: 6] ls /tasks
[task-0000000000]
[zk: 7] ls /workers
[worker1.example.com]
[zk: 8] create /assign/worker1.example.com/task-0000000000 ""
Created /assign/worker1.example.com/task-0000000000
[zk: 9]

从节点接收到新任务分配的通知:

[zk: localhost:2181(CONNECTED) 3]
WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged
path:/assign/worker1.example.com

从节点之后便开始检查新任务,并确认该任务是否分配给自己:

WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged
path:/assign/worker1.example.com

[zk: localhost:2181(CONNECTED) 3] ls /assign/worker1.example.com
[task-0000000000]
[zk: localhost:2181(CONNECTED) 4]

一旦从节点完成任务的执行,它就会在/tasks中添加一个状态znode:

[zk: localhost:2181(CONNECTED) 4] create /tasks/task-0000000000/status "done"
Created /tasks/task-0000000000/status
[zk: localhost:2181(CONNECTED) 5]

之后,客户端接收到通知,并检查执行结果:

WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged
path:/tasks/task-0000000000

[zk: localhost:2181(CONNECTED) 2] get /tasks/task-0000000000
"cmd"
cZxid = 0x7c
ctime = Tue Dec 11 10:30:18 CET 2012
mZxid = 0x7c
mtime = Tue Dec 11 10:30:18 CET 2012
pZxid = 0x7e
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 1
[zk: localhost:2181(CONNECTED) 3] get /tasks/task-0000000000/status
"done"
cZxid = 0x7e
ctime = Tue Dec 11 10:42:41 CET 2012
mZxid = 0x7e
mtime = Tue Dec 11 10:42:41 CET 2012
pZxid = 0x7e
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 0
[zk: localhost:2181(CONNECTED) 4]

客户端检查状态znode的信息,并确认任务的执行结果。本例中,我们看到任务成功执行,其状态为“done”。当然任务也可能非常复杂,甚至涉及另一个分布式系统。最终不管是什么样的任务,执行任务的机制与通过ZooKeeper来传递结果,本质上都是一样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值