深入ZooKeeper——开始使用ZooKeeper的API

设置ZooKeeper的CLASSPATH

我们需要设置正确的classpath,以便运行或编译ZooKeeper的Java代码。除了ZooKeeper的JAR包外,ZooKeeper使用了大量的第三方库。为了简化输入和方便阅读,我们使用环境变量CLASSPATH来表示所有必需的库。
ZooKeeper发行包中bin目录下的zkEnv.sh脚本会为我们设置该环境变量。我们需要使用以下方式来编码:
在这里插入图片描述
(在Windows上,使用call命令调用,而不是使用zkEnv.cmd脚本。)
一旦运行这个脚本,环境变量CLASSPATH就会正确设置。我们在编译和运行Java程序时用到它。

建立ZooKeeper会话

ZooKeeper的API围绕ZooKeeper的句柄(handle)而构建,每个API调用都需要传递这个句柄。这个句柄代表与ZooKeeper之间的一个会话。
创建ZooKeeper句柄的构造函数如下所示:
在这里插入图片描述

  • connectString
    包含主机名和ZooKeeper服务器的端口。我们之前通过zkCli连接ZooKeeper服务时,已经列出过这些服务器。
  • sessionTimeout
    以毫秒为单位,表示ZooKeeper等待客户端通信的最长时间,之后会声明会话已死亡。
  • watcher
    用于接收会话事件的一个对象,这个对象需要我们自己创建。
    Wacher被定义为接口,所以我们需要自己实现一个类,然后初始化这个类的实例并传入ZooKeeper的构造函数中。客户端使用Watcher接口来监控与ZooKeeper之间会话的健康情况。与ZooKeeper服务器之间建立或失去连接时就会产生事件。它们同样还能用于监控ZooKeeper数据的变化。最终,如果与ZooKeeper的会话过期,也会通过Watcher接口传递事件来通知客户端的应用。

实现一个Watcher
为了从ZooKeeper接收通知,我们需要实现监视点。首先让我们进一步了解Watcher接口,该接口的定义如下
在这里插入图片描述
这个接口没有多少内容,我们不得不自己实现,但现在我们只是简单地输出事件。所以,让我们从一个名为Master的类开始实现示例:

import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.Watcher;
public class Master implements Watcher {
	ZooKeeper zk;
	String hostPort;
	Master(String hostPort) {
		this.hostPort = hostPort;}
	void startZK() {
		zk = new ZooKeeper(hostPort, 15000, this);
	}
	public void process(WatchedEvent e) {
		System.out.println(e);}
	public static void main(String args[])
		throws Exception {
		Master m = new Master(args[0]);
		m.startZK();
		// wait for a bit
		Thread.sleep(60000);
	}
}

①在构造函数中,我们并未实例化ZooKeeper对象,而是先保存hostPort留着后面使用。Java最佳实践告诉我们,一个对象的构造函数没有完成前不要调用这个对象的其他方法。因为这个对象实现了Watcher,并且当我们实例化ZooKeeper对象时,其Watcher的回调函数就会被调用,所以我们需要Master的构造函数返回后再调用ZooKeeper的构造函数。
②这个简单的示例没有提供复杂的事件处理逻辑,而只是将我们收到的事件进行简单的输出。

通过以下方式就可以编译这个简单的例子:
在这里插入图片描述
当我们编译完Master.java这个文件后,运行它并查看结果:
在这里插入图片描述
ZooKeeper客户端API产生很多日志消息,使用户可以了解发生了什么。

ZooKeeper管理连接
请不要自己试着去管理ZooKeeper客户端连接。ZooKeeper客户端库会监控与服务之间的连接,客户端库不仅告诉我们连接发生问题,还会主动尝试重新建立通信。一般客户端开发库会很快重建会话,以便最小化应用的影响。所以不要关闭会话后再启动一个新的会话,这样会增加系统负载,并导致更长时间的中断。

获取管理权

为了确保同一时间只有一个主节点进程出于活动状态,我们使用ZooKeeper来实现简单的群首选举算法。
ZooKeeper通过插件式的认证方法提供了每个节点的ACL策略功能,因此,如果我们需要,就可以限制某个用户对某个znode节点的哪些权限。
当然,我们希望在主节点死掉后/master节点会消失,我们可以使用ZooKeeper
的临时性znode节点来达到我们的目的。
因此,我们将会在我们的程序中添加以下代码:

String serverId = Integer.toHexString(random.nextInt());
void runForMaster() {
	zk.create("/master",①
		serverId.getBytes(),②
		OPEN_ACL_UNSAFE,③
		CreateMode.EPHEMERAL);}

①我们试着创建znode节点/master。如果这个znode节点存在,create就会失败。同时我们想在/master节点的数据字段保存对应这个服务器的唯一ID。
②数据字段只能存储字节数组类型的数据,所以我们将int型转换为一个字节数组。
③如之前所提到的,我们使用开放的ACL策略。
④我们创建的节点类型为EPHEMERAL(临时)。

然而,我们这样做还不够,create方法会抛出两种异常:
KeeperException和InterruptedException。我们需要确保我们处理了这两种异常,特别是ConnectionLossException(KeeperException异常的子类)和InterruptedException。对于其他异常,我们可以忽略并继续执行,但对于这两种异常,create方法可能已经成功了,所以如果我们作为主节点就需要捕获并处理它们。

当处理ConnectionLossException异常时,我们需要找出那个进程创建的/master节点,如果进程是自己,就开始成为群首角色。我们通过getData方法来处理:
在这里插入图片描述

  • path
    类似其他ZooKeeper方法一样,第一个参数为我们想要获取数据的znode节点路径。
  • watch
    表示我们是否想要监听后续的数据变更。如果设置为true,我们就可以通过我们创建ZooKeeper句柄时所设置的Watcher对象得到事件,同时另一个版本的方法提供了以Watcher对象为入参,通过这个传入的对象来接收变更的事件。
    现在我们设置这个参数为false,因为我们现在我们只想知道当前的数据是什么。
  • stat
    最后一个参数类型Stat结构,getData方法会填充znode节点的元数据信息。

让我们按以下代码段来修改代码,在runForMaster方法中引入异常处理:

String serverId = Integer.toString(Random.nextLong());
boolean isLeader = false;
// returns true if there is a master
boolean checkMaster() {
	while (true) {
		try {
			Stat stat = new Stat();
			byte data[] = zk.getData("/master", false, stat);①
			isLeader = new String(data).equals(serverId));return true;
		} catch (NoNodeException e) {
			// no master, so try create again
			return false;
		} catch (ConnectionLossException e) {
		}
	}
}
void runForMaster() throws InterruptedException {while (true) {
		try {④
			zk.create("/master", serverId.getBytes(),
				OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);⑤
			isLeader = true;
			break;
		} catch (NodeExistsException e) {
			isLeader = false;
		break;
		} catch (ConnectionLossException e) {}
		if (checkMaster()) break;}
}

①通过获取/master节点的数据来检查活动主节点。
②该行展示了为什么我们需要使用在创建/master节点时保存的数据:如果/master存在,我们使用/master中的数据来确定谁是群首。如果一个进程捕获到ConnectionLossException,这个进程可能就是主节点,因create操作实际上已经处理完,但响应消息却丢失了。
③我们将InterruptedException异常简单地传递给调用者。
④我们将zk.create方法包在try块之中,以便我们捕获并处理ConnectionLossException异常。
⑤这里为create请求,如果成功执行将会成为主节点。
⑥处理ConnectionLossException异常的catch块的代码为空,因为我们并不想中止函数,这样就可以使处理过程继续向下执行。
⑦检查活动主节点是否存在,如果不存在就重试。

现在,我们看一下Master的main主函数:

public static void main(String args[]) throws Exception {
	Master m = new Master(args[0]);
	m.startZK();
	m.runForMaster();if (isLeader) {
		System.out.println("I'm the leader");// wait for a bit
		Thread.sleep(60000);
	} else {
		System.out.println("Someone else is the leader");
	}
	m.stopZK();
}

其他暂略。

猜你喜欢

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