对于Zookeeper的介绍和安装教程这里不再赘述,网上很多资料都介绍得很专业,上一篇文章在搭建Hadoop集群的时候也简单介绍了下。本篇主要内容如下:
1、Zookeeper客户端的基本操作
2、Zookeeper中api的使用
3、Zookeeper的选举流程
一、Zookeeper客户端的基本操作
进入zookeeper的安装目录下,在bin文件夹下面有一个zkCli.sh ,这个是zookeeper提供给客户端使用的脚步,在服务端已经启动好的情况下,直接启动这个脚本sh zkCli.sh,即可进入交互模式。
1、get:获取节点数据
[zk: localhost:2181(CONNECTED) 11] get /demo
hello
cZxid = 0x2000000b4
ctime = Mon May 13 12:11:16 CST 2019
mZxid = 0x2000000b4
mtime = Mon May 13 12:11:16 CST 2019
pZxid = 0x2000000b4
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
各项说明:
cZxid :创建节点的id
ctime : 节点的创建时间
mZxid :修改节点的id
mtime :修改节点的时间
pZxid :子节点的id
cversion : 子节点的版本
dataVersion : 当前节点数据的版本
aclVersion :权限的版本
ephemeralOwner :判断是否是临时节点
dataLength : 数据的长度
numChildren :子节点的数量
2、create:创建节点
基本格式:create [-s] [-e] path data acl
创建持久化节点目录:
[zk: localhost:2181(CONNECTED) 9] create /demo hello
Created /demo
[zk: localhost:2181(CONNECTED) 11] get /demo
hello
cZxid = 0x2000000b4
ctime = Mon May 13 12:11:16 CST 2019
mZxid = 0x2000000b4
mtime = Mon May 13 12:11:16 CST 2019
pZxid = 0x2000000b4
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
创建临时节点目录:create -e
[zk: localhost:2181(CONNECTED) 19] create -e /tempnode hello
Created /tempnode
[zk: localhost:2181(CONNECTED) 20] get /tempnode
hello
cZxid = 0x2000000b7
ctime = Mon May 13 12:26:18 CST 2019
mZxid = 0x2000000b7
mtime = Mon May 13 12:26:18 CST 2019
pZxid = 0x2000000b7
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0xc969fb77db58000a
dataLength = 5
numChildren = 0
创建PERSISTENT_SEQUENTIAL类型的节点: create -s
[zk: localhost:2181(CONNECTED) 22] create -s /demo/sec seq
Created /demo/sec0000000000
[zk: localhost:2181(CONNECTED) 23] create -s /demo/sec seq
Created /demo/sec0000000001
[zk: localhost:2181(CONNECTED) 24] create -s /demo/sec seq
Created /demo/sec0000000002
[zk: localhost:2181(CONNECTED) 25] ls /demo
[sec0000000000, sec0000000001, sec0000000002]
ls命令显示查看当前节点情况。可以看到这里创建了顺序节点,顺序节点会自动累加。
3、修改节点:set path data [version]
[zk: localhost:2181(CONNECTED) 26] get /demo
hello
cZxid = 0x2000000b4
ctime = Mon May 13 12:11:16 CST 2019
mZxid = 0x2000000b4
mtime = Mon May 13 12:11:16 CST 2019
pZxid = 0x2000000ba
cversion = 3
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 3
[zk: localhost:2181(CONNECTED) 27] set /demo hello-1
cZxid = 0x2000000b4
ctime = Mon May 13 12:11:16 CST 2019
mZxid = 0x2000000bb
mtime = Mon May 13 12:36:04 CST 2019
pZxid = 0x2000000ba
cversion = 3
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 9
numChildren = 3
修改后,节点内容变为了新的内容,并且数据版本dataVersion由0变为了1,我们再来修改指定版本的数据内容:
[zk: localhost:2181(CONNECTED) 28] set /demo hello-2 0
version No is not valid : /demo
看到这里我们对0版本的数据进行更新会出错,表示版本不可用了,这是因为上一步我们已经改了一次,数据版本变为了1,我们把命令中的0换成1试试:
[zk: localhost:2181(CONNECTED) 29] set /demo new-hello2 1
cZxid = 0x2000000b4
ctime = Mon May 13 12:11:16 CST 2019
mZxid = 0x2000000bd
mtime = Mon May 13 12:41:33 CST 2019
pZxid = 0x2000000ba
cversion = 3
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 10
numChildren = 3
可以看到这样就可以了,这就是指定版本的数据修改。
4、删除节点:delete path [version]
[zk: localhost:2181(CONNECTED) 34] ls /demo
[sec0000000000, sec0000000001, sec0000000002]
[zk: localhost:2181(CONNECTED) 35] delete /demo/sec0000000000
[zk: localhost:2181(CONNECTED) 36] delete /demo/sec0000000001
[zk: localhost:2181(CONNECTED) 37] delete /demo/sec0000000002
[zk: localhost:2181(CONNECTED) 38] ls /demo
[]
二、Zookeeper中api的使用
1、java中对节点的增、删、改、查
package com.lzb.demo;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
public class MyZKClient {
public static void main(String[] args) {
final CountDownLatch countDownLatch = new CountDownLatch(1);
try {
String servers = "192.168.218.106,192.168.218.107,192.168.218.108,192.168.218.109";
ZooKeeper zk = new ZooKeeper(servers, 3000, new Watcher() {
public void process(WatchedEvent watchedEvent) {
if(watchedEvent.getState() == Event.KeeperState.SyncConnected){
countDownLatch.countDown();
}
}
});
System.out.println("await...");
countDownLatch.await();
System.out.println("zk.state==="+zk.getState());
String node = "/app";
Stat stat = zk.exists(node, false);
if(stat == null){
System.out.println("创建节点...");
String createResult = zk.create(node, "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("createResult==="+createResult);
stat = zk.exists(node, false);
}
byte[] data = zk.getData(node, false, stat);
System.out.println("当前(data,version)===(" + new String(data) + "," + stat.getVersion() + ")");
stat = zk.setData(node, "hello-update".getBytes(), stat.getVersion());
byte[] data2 = zk.getData(node, false, stat);
System.out.println("更新后(data,version)===(" + new String(data2) + "," + stat.getVersion() + ")");
zk.delete(node,stat.getVersion());
stat = zk.exists(node, false);
if(stat == null){
System.out.println("delete complete!");
}
zk.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
await...
Watch===None
zk.state===CONNECTED
创建节点...
createResult===/app
当前(data,version)===(hello,0)
更新后(data,version)===(hello-update,1)
delete complete!
Process finished with exit code 0
上面的代码只是一个简单的增删改查,当我们想要监控某个节点的情况的时候,得用到zookeeper里面的一个叫Watcher的类,它相当于为某个节点的某个事件注册一个监听器,当事件触发的时候,就会回调这个Watcher。
2、使用Watcher监听节点的情况
Watcher 设置是开发中最常见的,需要搞清楚watcher的一些基本特征,对于exists、getdata、getchild对于节点的不同操作会收到不同的 watcher信息:
state=-112 会话超时状态
state= -113 认证失败状态
state= 1 连接建立中
state= 2 (暂时不清楚如何理解这个状态,ZOO_ASSOCIATING_STATE)
state=3 连接已建立状态
state= 999 无连接状态
type=1 创建节点事件
type=2 删除节点事件
type=3 更改节点事件
type=4 子节点列表变化事件
type= -1 会话session事件
type=-2 监控被移除事件
对父节点的变更以及孙节点的变更都不会触发watcher,而对watcher本身节点以及子节点的数量变更会触发watcher,具体参照下表:
下面我们通过来演示watcher对节点的监控,代码如下:
package com.lzb.demo;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class NodeMonitor {
private ZooKeeper zk;
private String servers = "192.168.218.106,192.168.218.107,192.168.218.108,192.168.218.109";
private int sessionTimeout = 30*60*1000;
private String node;
/**
* 开始监控
* @param node 节点路径
*/
public void startMonitor(String node){
this.node = node;
connectZookeeper();
monitorExist();
monitorGetData();
monitorGetChildren();
//目的是让程序不终止
while (true){
try {
Thread.sleep(sessionTimeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 连接zookeeper
*/
private void connectZookeeper(){
try {
final CountDownLatch countDownLatch = new CountDownLatch(1);
zk = new ZooKeeper(servers, sessionTimeout, new Watcher() {
public void process(WatchedEvent watchedEvent) {
System.out.println(watchedEvent.getState());
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
countDownLatch.countDown();
}
}
});
countDownLatch.await();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 监听节点触发exist的事件
*/
private void monitorExist() {
System.out.println("触发事件==>回调exist");
try {
Stat stat = zk.exists(node, new Watcher() {
public void process(WatchedEvent event) {
//注意因为Watcher的使用是一次性的,也就是说使用后它就会被移除,所以这里我们需要再次挂上监听器
monitorExist();
}
});
if(stat == null){
System.out.println(node + " 节点不存在");
}else {
System.out.println(node + " 节点存在");
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 监听节点触发getData的事件
*/
private void monitorGetData() {
System.out.println("触发事件==>回调getData");
try {
Stat stat = zk.exists(node, false);
if(stat == null) {
System.out.println(node + " 节点不存在");
return;
}
byte[] bytes = zk.getData(node, new Watcher() {
public void process(WatchedEvent event) {
//注意因为Watcher的使用是一次性的,也就是说使用后它就会被移除,所以这里我们需要再次挂上监听器
monitorGetData();
}
}, null);
String data = new String(bytes);
System.out.println(node + "节点的数据 === " + data);
} catch(Exception e) {
e.printStackTrace();
}
}
/***
* 监听节点触发getChildren的事件
*/
private void monitorGetChildren() {
System.out.println("触发事件==>回调getChildren");
try {
Stat stat = zk.exists(node, false);
if(stat == null){
System.out.println(node + " 节点不存在");
return;
}
List children = zk.getChildren(node, new Watcher() {
public void process(WatchedEvent event) {
//注意因为Watcher的使用是一次性的,也就是说使用后它就会被移除,所以这里我们需要再次挂上监听器
monitorGetChildren();
}
});
System.out.print(node + "节点的子节点 === [");
for (String name : children ) {
System.out.print(name + " ");
}
System.out.print("]\n");
}catch(Exception e) {
e.printStackTrace();
}
}
}
为了实现能自动挂上监听器,这里我们先通过zkCli.sh客户端在zookeeper上创建一个节点"/app":
[zk: localhost:2181(CONNECTED) 99] create /app hello-app
Created /app
然后运行主类代码:
package com.lzb.demo;
public class Demo {
public static void main(String[] args) {
NodeMonitor monitor = new NodeMonitor();
monitor.startMonitor("/app");
}
}
打印信息如下:
接着我们在客户端上更新"/app"的数据:
[zk: localhost:2181(CONNECTED) 104] set /app hello-app-new
cZxid = 0x200000170
ctime = Tue May 14 13:59:16 CST 2019
mZxid = 0x200000174
mtime = Tue May 14 14:01:21 CST 2019
pZxid = 0x200000170
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 13
numChildren = 0
[zk: localhost:2181(CONNECTED) 105]
控制台这时打印信息如下:
现在我们在"/app"下创建3个子节点:
[zk: localhost:2181(CONNECTED) 105] create /app/sub1 hello-sub1
Created /app/sub1
[zk: localhost:2181(CONNECTED) 106] create /app/sub2 hello-sub2
Created /app/sub2
[zk: localhost:2181(CONNECTED) 107] create /app/sub3 hello-sub3
Created /app/sub3
[zk: localhost:2181(CONNECTED) 108] ls /app
[sub2, sub3, sub1]
这时控制台打印信息如下:
然后我们随便修改一个子节点的数据或者创建一个孙节点:
[zk: localhost:2181(CONNECTED) 109] set /app/sub1 hello-sub1-new
cZxid = 0x200000175
ctime = Tue May 14 14:04:30 CST 2019
mZxid = 0x200000178
mtime = Tue May 14 14:07:41 CST 2019
pZxid = 0x200000175
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 14
numChildren = 0
[zk: localhost:2181(CONNECTED) 113] create /app/sub1/ssub1 hello-ssub1
Created /app/sub1/ssub1
但是发现控制台依旧还是上面的那样,并没有打印子节点的数据。这是因为我们在代码中是对"/app"这个节点进行监控的,对其子节点的数据修改触发不了本节点的事件,所以才会出现这种情况,如果想获取其子节点的数据信息可以单独监控这个子节点来即可。
最后我们依次删除子节点:
[zk: localhost:2181(CONNECTED) 133] delete /app/sub1/ssub1
[zk: localhost:2181(CONNECTED) 134] delete /app/sub1
[zk: localhost:2181(CONNECTED) 135] delete /app/sub2
[zk: localhost:2181(CONNECTED) 136] delete /app/sub3
[zk: localhost:2181(CONNECTED) 139] delete /app
控制台打印信息如下:
OK,这就是zookeeper的Watcher监听器的使用。
三、Zookeeper的选举流程
Zookeeper的选举算法有三种:LeaderElection、AuthFastLeaderElection、FastLeaderElection。
其中FastLeaderElection是默认的算法,前两种是过时了的。这里只简要罗列一下FastLeaderElection的选举流程(详细介绍请参见http://www.cnblogs.com/lpshou/archive/2013/06/14/3136738.html):
假设有五台服务器组成的zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的.假设这些服务器依序启动,来看看会发生什么:
1. 服务器1启动,此时只有它一台服务器启动了,它发出去的报没有任何响应,所以它的选举状态一直是LOOKING状态
2. 服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1,2还是继续保持
LOOKING状态.
3. 服务器3启动,根据前面的理论分析,服务器3成为服务器1,2,3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的leader.
4. 服务器4启动,根据前面的分析,理论上服务器4应该是服务器1,2,3,4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接收当小弟的命了.
5. 服务器5启动,同4一样,当小弟.