以后几节中主要介绍以下内容:
- 如何执行领导者选举,组员管理和两阶段提交协议等常见的分布式系统任务
- 如何实现一些分布式数据结构,如屏障(barrier),锁(lock)和队列(queue)
这一章中概述的高层次构建也被称为『ZooKeeper recipes』。这些都是在客户端使用ZooKeeper的编程模型实现的,并且不需要从服务器端获得特别的支持。在没有ZooKeeper和它的API的情况下,这些recipes的实现将会是相当复杂和困难的。
一些第三方和社区开发的ZooKeeper客户端绑定也提供这些高级分布式系统的构建作为其客户端类库的一部分。 例如,Netflix Curator是ZooKeeper的一个功能丰富的Java客户端框架。
ZooKeeper发行版本中附带了领导选举和分发锁和队列的recipes,这些可以在分布式应用程序中使用。 这三个recipes的Java实现可以在发行版的recipes
目录中找到。
ZooKeeper recipes
在本节中学习使用ZooKeeper来开发高级分布式系统构建和数据结构。正如前面提到的,这些构建和方法在建立可伸缩的分布式体系结构中是非常重要的,但是从头开始实现它们是相当复杂的。开发人员常常会在实现这些和集成他们的应用程序逻辑时陷入困境。在本节中,将学习如何使用ZooKeeper的数据模型和原语构建一些高级功能,并了解管理员如何使其简单、可伸缩和没有错误,而且代码量更少。
1. Barrier
Barrier是分布式系统中使用的一种同步方法,用于阻塞一组节点的处理,直到满足条件。它定义了一个点,所有节点必须停止它们的处理,直到所有其他节点到达这个Barrier时才进行处理。
使用ZooKeeper实现屏障的Barrier如下:
- 首先,将znode指定为屏障znode,例如
/zk_barrier
。 - 如果这个屏障znode存在,则说屏障在系统中是活跃的。
- 每个客户端通过在屏障 znode上注册监视事件(监视事件设置为true),在
/zk_barrier
上调用ZooKeeper API的exists()
方法。 - 如果
exists()
方法返回false,则说明屏障不再存在,客户端继续运算。 - 否则,如果
exists
方法返回true,则客户端等待监视事件。 - 当屏障条件满足退出时,负责屏障的客户端将删除
/zk_barrier
。 - 删除触发监视事件,并且在获取此通知时,客户端再次调用
/zk_barrier
上的exists()
方法。 - 步骤7返回true,客户端可以继续进行。
Note
屏障一直存在,直到屏障znode终止存在!
通过这种方式,我们可以不费力地使用ZooKeeper来实现一个屏障。
到目前为止所举的例子是一个简单的屏障,它可以阻止一组分布式进程在某些条件下等待,然后在条件满足时进行处理。还有另一种类型的障碍有助于同步计算的开始和结束;这就是所谓的双重屏障。双重屏障的逻辑表明,当需要的进程数量加入屏障时,计算就开始了。在完成计算后,进程会离开,当参与屏障的进程数变为0时,计算就会结束。
双重屏障的算法是通过具有屏障znode来实现的,该屏障znode的作用是作为参与计算的个体过程znode的父节点。 其算法概述如下:
阶段1:加入屏障znode的方式如下:
- 假设屏障znode由
znode/barrier
表示。 每个客户端进程通过创建一个以/barrier
作为父节点的ephemeral znode来注册。 在真实情况下,客户端可能使用主机名进行注册。 - 客户端进程为在
/barrier
节点下的另一个存在的‘ready`节点设置监视事件,等待节点的出现。 - 数字N是在系统中预定义的; 这是在开始计算之前管理加入屏障的最小数量的客户端。
- 在加入屏障时,每个客户端进程查找
/barrier
的子节点数量:M = getChildren(/barrier, watch=false)
。 - 如果M小于N,则客户端等待步骤3中注册的监视事件。
- 否则,如果M等于N,则客户端进程在
/barrier
下创建ready
znode。 - 5步中创建的
ready
节点会触发监视事件,每个客户端都会启动他们到目前为止所做的计算。
阶段2:离开屏障的方式如下:
- 在完成计算的过程中,客户端进程删除了在
/barrier
下创建的znode(在第1阶段的第2步:加入屏障)。 - 客户进程接着查找
/barrier
节点的子节点数量:M = getChildren(/barrier, watch=True)
如果M不等于0,则该客户端等待通知(注意,在前面的调用中,已将监视事件设置为True)。
如果M等于0,则客户端退出屏障znode。
前面的程序有一种潜在的羊群效应,当触发通知时,所有的客户端进程都将唤醒以检查在barrier中留下的子节点的数量。为了避免这种情况,我们可以使用在第1阶段第2步中创建的sequential ephemeral znode加入barrier。每一个客户端进程都要注意它的下一个最短的sequential ephemeral的znode作为退出标准。这样,为任何完成计算的客户端只生成一个事件,因此,并不是所有的客户端都需要一起醒来检查它的退出条件。对于大量参与到一个barrier中的客户端进程,羊群效应会对ZooKeeper服务的可伸缩性产生负面影响,开发人员应该意识到这种情况。
Note
双重屏障的Java语言实现可以在http://zookeeper.apache.org/doc/r3.4.6/zookeeperTutorial.html的ZooKeeper文档中找到。