概要
分布式应用的一个主要难点是“部分失败”。例如通过消息中间件,网络中的一个节点向另一个节点发送消息,发送节点很难知道消息是否成功送达,什么时候送达,接收节点是否正常运行,消息是否被正确处理,什么时候被处理等。ZooKeeper被用来在分布式应用中提供分布式协调服务,使用ZooKeeper本身并不能避免“部分失败”发生,因为“部分失败”是分布式应用的因有属性。相反,ZooKeeper提供一系列的工具,在分布式应用中安全处理“部分失败”。
ZooKeeper特性:
- 简单:ZooKeeper核心是一个文件系统,支持一些简单操作及一些特别的抽象概念如“命令”、“通知”等。
- 富于表现力:ZooKeeper提供了丰富的工具用来构建多种类型的协调数据结构及服务,如分布式队列、分布式锁、领导人选举等。
- 高可用:ZooKeeper被设计成高可用集群,可以避免单点故障。
- 解耦:应用的参考者不需要知道彼此的存在,甚至不需要在同一时间存在。
- 开源库:对于通用协调模型,ZooKeeper提供了通用实现。
- 高效
安装运行ZooKeeper
单机版ZooKeeper在Linux上安装非常简单。首先需要Java 6及以上版本的运行时环境。下载ZooKeeper软件包并解压到指定目录。设置环境变量,将ZooKeeper中的可执行文件路径加入到PATH环境变量中。
% export ZOOKEEPER_INSTALL=/home/tom/zookeeper.x.y.z
% export PATH=$PATH:$ZOOKEEPER_INSTALL/bin
在安装目录的子目录conf下找到zoo.cfg文件,没有自行创建,这个是默认的配置文件。在其中加入如下三行:
tickTime=2000
dataDir=/Users/tom/zookeeper
clientPort=2181
以上是单机ZooKeeper需要的最小配置。简单点说,tickTime是ZooKeeper中的最小时间单位,其本向单位是毫秒。dataDir是ZooKeeper持久化数据的本地文件目录。clientPort是对外提供服务的端口号。执行如下命令启动ZooKeeper服务:
% zkServer.sh start
检查ZooKeeper是否正常运行:
% echo ruok | nc localhost 2181
imok
ZooKeeper常用命令由四个字符组成,如下表:
| 类型 | 命令 | 描述 |
|---|---|---|
| 服务状态 | ruok | 服务正确输出imok |
| conf | 输出配置文件内容 | |
| envi | 输出环境信息,如java版本、ZooKeeper版本等 | |
| srvr | 输出统计信息,如延时统计、znode个数,节点运行模式等 | |
| stat | 输出统计信息及客户端连接等 | |
| srst | 重置统计信息 | |
| isro | 显示服务模式:只读模式、读写模式等。 | |
| 客户端连接 | dump | 显示全部会话、临时znode等,必需连接到主节点。 |
| cons | 显示客户端连接统计信息 | |
| crst | 重置客户端连接统计信息 | |
| 监视 | wchs | 显示服务监视的概略信息 |
| wchc | 列出所有对连接的监视,可能会影响性能 | |
| wchp | 通过znode的路径列出所有监视,可能会影响性能 | |
| 监控 | mntr | 系统性能监视,往住作为其它监视系统的数据源。 |
组成员例子
假设通过一组实例对外提供服务。我们希望客户端能够定位到一组服务中的一个实例并使用它。有两个主要问题。一个是成员列列管理问题,不能将成员列表管理在网络中的单个节点上,否则会出现单点故障。另一个是成员列表中成员的增减问题。服务实例本身可以向成员列表中自注册,但不能自己删除自己,比如在自己崩溃的情况下。我们需要一种主动的机制,在成员列表中的实例状态发生变化时,能够自动更新成员列表。ZooKeeper提供此类功能。
ZooKeeper中的znode概念。
ZooKeeper可以被看成是一个高可用的文件系统。它也有类似于文件系统中的目录节点、文件节点的概念。在ZooKeeper中其被称之为znode。znode即可像文件一样包含数据,也可以像目录一样包含其它znode。znode可以构成一个分层的名称空间,那么,最自然的管理成员列表的方法就是用组名称创建一个znode,其下的孩子znode则代表列表成员。本例中只需用到znode的名称,无需在其中存储数据。
创建组
通过ZooKeeper提供的Java API创建如下图一所示的znode。

图一
代码如下:
public class CreateGroup implements Watcher {
private static final int SESSION_TIMEOUT = 5000;
private ZooKeeper zk;
private CountDownLatch connectedSignal = new CountDownLatch(1);
public void connect(String hosts) throws IOExeption, InterruptedException {
zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);
connectedSignal.await();
}
@Override
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected) {
connectedSignal.countDown();
}
}
public void create(String groupName) throws KeeperException,
InterruptedException {
String path = "/" + groupName;
String createdPath = zk.create(path, null/*data*/, Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
System.out.println("Created " + createdPath);
}
public void close() throws InterruptedException {
zk.close();
}
public static void main(String[] args) throws Exception {
CreateGroup createGroup = new CreateGroup();
createGroup.connect(args[0]);
createGroup.create(args[1]);
createGroup.close();
}
}
代码解析:
在main函数中首先创建CreateGroup实例,然后调用connect成员函数。connect成员函数创建ZooKeeper实例,代表与ZooKeper服务器的连接。它需要三个参数,第一个参数是服务器地址,默认端口号2181。第二个参数是超时值,这里是5秒。每三个参数是监视器,这里就是新创建的CreateGroup实例。
在创建ZooKeeper实例时,会启动一个线程与服务器连接,并且调用立刻返回。因此需要手动添加一个等待器,等连接完成以后再返回。这里使用Java的CountDownLatch类实现。其实例connectedSignal的初始数值为1,在调用connect函数时,调用函灵长connectedSignal.await()等侍其值变成0。当新启动的线程连接到服务器并完成后,会回调监视器就是process函数,当process函数收到keeperState.SyncConnected时,表示连接已经建立,调用connectedSignal.countDown()减少其初始值1到0,此时connectedSignal.await()返回,并且connect函数也返回。
接下来调用createGroup.create()函数。zk.create函数创建znode时需要四个参数:路径、znode内容、访问控制、类型。类型主要有两类,一类是临时性的,当连接失效时,ZooKeeper服务自动删除此类znode,另一类是持久性的,当连接失效时并不自动删除,除非显示删除。
加入组
每个成员作为一个单独的应用,在运行时需要将自己加入到组成员列表中,并且当应用退出时,自动从组成员列表中删除,因此我们创建一个临时znode。代码如下,ConnectionWatcher表示如何创建ZooKeeper实例及如何连接服务,省略:
public class JoinGroup extends ConnectionWatcher {
public void join(String groupName, String memberName) throws KeeperException,
InterruptedException {
String path = "/" + groupName + "/" + memberName;
String createPath = zk.create(path, null/*data*/, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println("Created " + createPath);
}
public static void main(String[] args) throws Exception {
JoinGroup joinGroup = new JoinGroup();
joinGroup.connected(args[0]);
joinGroup.join(args[1], args[2]);
Thread.sleep(Long.MAX_VALUE);
}
}
列出组成员
代码如下,省略实现连接建立的逻辑ConnectionWatcher类:
public class ListGroup extends ConnectionWatcher {
public void list(String groupName) throws KeeperException,
InterruptedException {
String path = "/" + groupName;
try {
List<String> children = zk.getChildren(path, false);
if (children.isEmpty()) {
System.out.printf("No members in group %s\n", groupName);
System.exit(1);
}
for(String child: children) {
System.out.println(child);
}
} catch (KeeperException.NoNodeException e) {
System.out.print("Group %s does not exist\n", groupName);
System.exit(1);
}
}
public static void main(String[] args) throws Exception {
ListGroup listGroup = new ListGroup();
listGroup.connect(args[0]);
listGroup.list(args[1]);
listGroup.close();
}
}
删除组
代码如下:
public class DeleteGroup extends ConnectionWatcher {
public void delete(String groupName) throws KeeperException,
InterruptedException {
String path = "/" + groupName;
try {
List<String> children = zk.getChildren(path, false);
for (String child: children) {
zk.delete(path + "/" + child, -1);
}
zk.delete(path, -1);
} catch (KeeperException.NoNodeException e) {
System.out.printf("Group %s does not exist\n", groupName);
System.exit(1);
}
}
public static void main(String[] args) throws Exception {
DeleteGroup deleteGroup = new DeleteGroup();
deleteGroup.connect(args[0]);
deleteGroup.delete(args[1]);
deleteGroup.close();
}
}
本文介绍ZooKeeper在分布式应用中的作用,包括其特性、安装配置、基本命令及组成员管理示例,展示了如何利用ZooKeeper解决部分失败难题。
702

被折叠的 条评论
为什么被折叠?



