在之前的介绍中,我们操作Zookeeper的节点都是直接在服务器上,只用zkCli.sh脚本连接上服务端直接进行操作的,那么我们如何在项目中来操作节点信息呢?这里就需要用到我们的Zookeeper客户端了,这里我们首先介绍的是Zookeeper提供的原生客户端,是由Zookeeper官方提供的java客户端API
在使用客户端连接Zookeeper服务时,我们要明确的是客户端与服务端的一次会话连接,本质是TCP长连接,通过会话可以进行心跳检测和数据传输。会话(session)是Zookepper非常重要的概念,客户端和服务端之间的任何交互操作都与会话有关。
一旦客户端开始创建Zookeeper对象,那么客户端状态就会变成CONNECTING
状态,同时客户端开始尝试连接服务端,连接成功后,客户端状态变为CONNECTED
,通常情况下,由于断网或其他原因,客户端与服务端之间会出现断开情况,一旦碰到这种情况,Zookeeper客户端会自动进行重连服务,同时客户端状态再次变成CONNECTING
,直到重新连上服务端后,状态又变为CONNECTED
。
在通常情况下,客户端的状态总是介于CONNECTING
和CONNECTED
之间。但是,如果出现诸如会话超时、权限检查或是客户端主动退出程序等情况,客户端的状态就会直接变更为CLOSE
状态
首先我们新建一个项目,然后引入Zookeeper的依赖,如下:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
</dependency>
然后我们就可以向上述所说的一样,进行创建Zookeeper对象,然后开始连接服务端,但是需要注意的是原生的Zookeeper客户端连接服务端的步骤是异步执行的,所以我们在连接服务端之后,不可以立即进行节点操作等相关步骤,因为我们不确定什么时候连接才能真正的成功。
这里我们就需要进行一定的处理的,之前我们在Netty中介绍 使用Netty实现通信协议时,在其业务处理时,使用Netty客户端连接Netty服务端,这里我们也是进行了相关的处理,等待链路成功建立后在进行相关操作
在这里我们使用了并发编程中的 CountDownLatch 来解决该问题,如下:
public class ZookeeperTest {
public static final String CONNECT_ADDR = "192.168.80.130:2181";
public static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws Exception {
ZooKeeper zooKeeper = new ZooKeeper(CONNECT_ADDR, 5000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if(event.getState() == Event.KeeperState.SyncConnected){
countDownLatch.countDown();
}
}
});
System.out.println(zooKeeper.getState());
countDownLatch.await();
System.out.println("连接成功..." + zooKeeper.getState());
}
}
创建节点
这样就可以保证我们在成功连接后,在进行相关的操作了,这里我们首先看看连接后,如何创建一个新的节点,首先我们可以看到下图红框中的红线处,就是我们在服务器上手动创建节点所必须的节点名称和节点内容
那么另外两个(红色箭头处)是什么呢?这里分别是我们之前介绍的节点的权限和节点的类型,首先看第一个ZooDefs.Ids.OPEN_ACL_UNSAFE , 我们进入其中发现就是我们在ACL权限控制中介绍的
而第二个CreateMode.PERSISTENT 就是我们之前介绍的节点类型,如持久节点、临时节点、顺序节点等等
获取节点数据
在执行过创建节点后,我们将其注释,否则创建相同节点会报错,然后我们再来看一看如何获取刚刚创建的节点的数据,其中watch: true
后续会介绍,我们先看一下 Stat()
这个Stat类就是用用于保存我们之前介绍的节点信息的,其中包括version、修改时间等等
运行结果如下:
修改节点数据
修改节点数据也是非常的简单,其中version:-1
表示不做版本控制
如果我们希望做版本控制的话也是非常简单的,我们就可以在获取节点数据时,获取其对应节点的Stat,从中获取其当前版本号即可,这个有点类似于在并发编程中介绍的CAS中的版本号
删除节点
删除节点也是非常的简单,如下:
获取子节点信息
下面我们手动建立了 /node1 节点,并且在 /node1 节点下建立了三个子节点n1、n2、n3,然后运行下列代码:
watch
这里我们再来介绍一下,我们一开始没有介绍的watch,在一开始使用客户端连接服务端的时候,我们利用了watch来使在真正成功连接后,在执行节点操作命令,达到了一个阻塞的目的。
从上述的用法我们也可以看出,watch有点类型于触发器,它可以监听一些特定的时间,比如我们现在来监听 /node1 节点,如果节点 /node1 发生了数据改变,我们就进行打印提示信息,如下
public class ZookeeperTest {
public static final String CONNECT_ADDR = "192.168.80.130:2181";
public static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws Exception {
ZooKeeper zooKeeper = new ZooKeeper(CONNECT_ADDR, 5000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if(event.getState() == Event.KeeperState.SyncConnected){
countDownLatch.countDown();
}
if(event.getType() == Event.EventType.NodeDataChanged){
//如果数据发生了变化
System.out.println("数据发生了改变->" + event.getPath());
}
}
});
countDownLatch.await();
}
}
然后我们就进行改变 /node1 节点中的数据,但是我们如果需要在改变 /node1 节点数据的前面,加上了一次 getData 的命令呢?因为我们没有专门的API去注册watcher,所以只能依附于增删改查API ,watch:true
表示我们这里使用的就是上述的watch
当然,这里我们也可以不使用上述的watch ,那么我们就需要自己定义一个 watch 了,如下:
另外值得一说的是,我们需要注意的是watch是一次性产品 , 什么意思呢?我们之前利用获取节点数据的方法 getData
在 /node1 节点上添加了watch,但是只能用一次,下次再用就必须再次的注册,如下:
这里如果我们希望每次都能监听到修改的操作,那么我们每次修改前就必须加上watch,如下: