建立个maven项目,pom.xml中引入zookeeper的jar。对于zookeeper中创建、删除、修改、查询数据都有同步和异步方式,下面写的例子,都是用同步的方式。如果想看异步、同步、权限控制等例子,可参考http://www.cnblogs.com/leesf456/p/6028416.html
<dependencies>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
</dependencies>
在resources文件夹下添加文件
log4j.properties,该文件可以在官网下载zookeeper.3.4.9.tar.gz,解压后,在conf文件夹下
# Define some default values that can be overridden by system properties
zookeeper.root.logger=INFO, CONSOLE
zookeeper.console.threshold=INFO
zookeeper.log.dir=.
zookeeper.log.file=zookeeper.log
zookeeper.log.threshold=DEBUG
zookeeper.tracelog.dir=.
zookeeper.tracelog.file=zookeeper_trace.log
#
# ZooKeeper Logging Configuration
#
# Format is "<default threshold> (, <appender>)+
# DEFAULT: console appender only
log4j.rootLogger=${zookeeper.root.logger}
# Example with rolling log file
#log4j.rootLogger=DEBUG, CONSOLE, ROLLINGFILE
# Example with rolling log file and tracing
#log4j.rootLogger=TRACE, CONSOLE, ROLLINGFILE, TRACEFILE
#
# Log INFO level and above messages to the console
#
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold}
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
#
# Add ROLLINGFILE to rootLogger to get log file output
# Log DEBUG level and above messages to a log file
log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLINGFILE.Threshold=${zookeeper.log.threshold}
log4j.appender.ROLLINGFILE.File=${zookeeper.log.dir}/${zookeeper.log.file}
# Max log file size of 10MB
log4j.appender.ROLLINGFILE.MaxFileSize=10MB
# uncomment the next line to limit number of backup files
#log4j.appender.ROLLINGFILE.MaxBackupIndex=10
log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
#
# Add TRACEFILE to rootLogger to get log file output
# Log DEBUG level and above messages to a log file
log4j.appender.TRACEFILE=org.apache.log4j.FileAppender
log4j.appender.TRACEFILE.Threshold=TRACE
log4j.appender.TRACEFILE.File=${zookeeper.tracelog.dir}/${zookeeper.tracelog.file}
log4j.appender.TRACEFILE.layout=org.apache.log4j.PatternLayout
### Notice we are including log4j's NDC here (%x)
log4j.appender.TRACEFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L][%x] - %m%n
1.1创建zookeeper连接
package com.fei.zk;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooKeeper;
public class ZkTest01 {
private static CountDownLatch latch = new CountDownLatch(1);
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
ZooKeeper zk = getZk();
while(true){
Thread.sleep(5000);
}
}
/**
* 创建zk连接
* @return
* @throws IOException
* @throws InterruptedException
*/
public static ZooKeeper getZk() throws IOException, InterruptedException{
//如果是集群,则逗号隔开,比如192.168.0.219:2181,192.168.0.220:2181,192.168.0.221:2181
String connStr = "192.168.0.219:2181";
int session_time = 5000;//每5秒发送一次心跳
ZooKeeper zk = new ZooKeeper(connStr,session_time,new Watcher(){
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected) {
System.out.println("zk connection OK....");
latch.countDown();//释放阻塞
} else if (event.getState().equals(KeeperState.Disconnected)) {
// 这时收到断开连接的消息,这里其实无能为力,因为这时已经和ZK断开连接了,只能等ZK再次开启了
System.out.println("zk Disconnected");
} else if (event.getState().equals(KeeperState.Expired)) {
// 这时收到这个信息,表示,ZK已经重新连接上了,但是会话丢失了,这时需要重新建立会话。
System.out.println("zk Expired");
//这里可以进行zk重新连接操作
//do Some thing....
} else if (event.getState().equals(KeeperState.AuthFailed)) {
System.out.println("zk AuthFailed");
}else {
System.out.println("unkonke...." + event.getType());
}
}
});
System.out.println("zk status=" + zk.getState());
latch.await(5000,TimeUnit.MILLISECONDS);//阻塞,等待zk连接成功,或者5s超时,否则不能往下执行
System.out.println("zk connection OK,lock release....");
return zk;
}
}
2017-02-22 15:52:44,393 [myid:] - INFO [main:ZooKeeper@438] - Initiating client connection, connectString=192.168.0.219:2181 sessionTimeout=5000 watcher=com.fei.zk.ZkTest01$1@44efc2d1
zk status=CONNECTING
2017-02-22 15:52:44,425 [myid:] - INFO [main-SendThread(192.168.0.219:2181):ClientCnxn$SendThread@1032] - Opening socket connection to server 192.168.0.219/192.168.0.219:2181. Will not attempt to authenticate using SASL (unknown error)
2017-02-22 15:52:44,436 [myid:] - INFO [main-SendThread(192.168.0.219:2181):ClientCnxn$SendThread@876] - Socket connection established to 192.168.0.219/192.168.0.219:2181, initiating session
2017-02-22 15:52:44,451 [myid:] - INFO [main-SendThread(192.168.0.219:2181):ClientCnxn$SendThread@1299] - Session establishment complete on server 192.168.0.219/192.168.0.219:2181, sessionid = 0x15a63f17b3c0001, negotiated timeout = 5000
zk connection OK....
zk connection OK,lock release....
连接成功了。如果把zk服务器停止掉,让客户端无法连接,会怎样?
2017-02-22 15:55:33,382 [myid:] - INFO [main-SendThread(192.168.0.219:2181):ClientCnxn$SendThread@1032] - Opening socket connection to server 192.168.0.219/192.168.0.219:2181. Will not attempt to authenticate using SASL (unknown error)
2017-02-22 15:55:34,411 [myid:] - WARN [main-SendThread(192.168.0.219:2181):ClientCnxn$SendThread@1162] - Session 0x0 for server null, unexpected error, closing socket connection and attempting reconnect
java.net.ConnectException: Connection refused: no further information
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
at sun.nio.ch.SocketChannelImpl.finishConnect(Unknown Source)
at org.apache.zookeeper.ClientCnxnSocketNIO.doTransport(ClientCnxnSocketNIO.java:361)
at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1141)
会不停的输出连接失败的日志,说明当zookeeper连接服务器失败时,它会不停的自动尝试连接。
1.2创建节点
/**
* 测试创建节点
* @param zk
*/
public static void createNode(ZooKeeper zk){
try {
//创建test节点,值为test,不需权限控制,值持久化
zk.create("/test", "test".getBytes("UTF-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("/test 创建成功....");
} catch (Exception e) {
System.out.println("/test 创建失败....");
e.printStackTrace();
}
//尝试创建已存在的节点
try {
//创建test节点,值为test,不需权限控制,值持久化
zk.create("/test", "test".getBytes("UTF-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("第二次 /test 创建成功....");
} catch (Exception e) {
System.out.println("第二次 /test 创建失败....");
e.printStackTrace();
}
//创建创建父节点不存在,就直接建子节点
try {
//创建/app/app01节点,值为test,不需权限控制,值持久化
zk.create("/app/app01", "app01".getBytes("UTF-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("/app/app01 创建成功....");
} catch (Exception e) {
System.out.println("/app/app01 /test 创建失败....");
e.printStackTrace();
}
}
zk connection OK....
zk connection OK,lock release....
/test 创建成功....
第二次 /test 创建失败....
org.apache.zookeeper.KeeperException$NodeExistsException: KeeperErrorCode = NodeExists for /test
at org.apache.zookeeper.KeeperException.create(KeeperException.java:119)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
at com.fei.zk.ZkTest01.createNode(ZkTest01.java:90)
at com.fei.zk.ZkTest01.main(ZkTest01.java:23)
/app/app01 /test 创建失败....
org.apache.zookeeper.KeeperException$NoNodeException: KeeperErrorCode = NoNode for /app/app01
at org.apache.zookeeper.KeeperException.create(KeeperException.java:111)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
at com.fei.zk.ZkTest01.createNode(ZkTest01.java:100)
at com.fei.zk.ZkTest01.main(ZkTest01.java:23)
可以看到,
如果节点已经存在了,再次创建就会抛出异常,如果父节点不存在就直接创建子节点,也会抛出异常.
CreateMode有PERSISTENT, PERSISTENT_SEQUENTIAL, EPHEMERAL, EPHEMERAL_SEQUENTIAL,其中 PERSISTENT,PERSISTENT_SEQUENTIAL服务器都会将数据进行持久化;EPHEMERAL, EPHEMERAL_SEQUENTIAL是临时的,在session中有效,也就是如果zk断开了或session过期了,那这些临时节点就不存在了;SEQUENTIAL就是序列,比如如果path写/app-,CreateMode用XXXX_SEQUENTIAL,那最终节点路径就是/app-0000000001(10个数字)
1.3判断节点是否存在
public static void isExist(ZooKeeper zk){
String path = "/test";
try {
Stat stat = zk.exists(path, false);
if(stat == null){
System.out.println("节点 path="+path+"不存在");
}else{
System.out.println("节点 path="+path+"存在,stat=" + stat);
}
} catch (KeeperException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
1.4删除节点
/**
* 删除节点
* @param zk
*/
public static void delete(ZooKeeper zk){
//删除已存在的节点
try {
Stat stat = zk.exists("/test", false);
zk.delete("/test", stat.getVersion());
System.out.println("删除/test成功....");
} catch (Exception e) {
System.out.println("删除/test节点失败...");
e.printStackTrace();
}
//删除不存在的节点
try {
zk.delete("/test/test", 0);
System.out.println("删除/test/test成功.....");
} catch (Exception e) {
System.out.println("删除/test/test失败.....");
e.printStackTrace();
}
}
zk connection OK....
zk connection OK,lock release....
删除/test成功....
删除/test/test失败.....
org.apache.zookeeper.KeeperException$NoNodeException: KeeperErrorCode = NoNode for /test/test
at org.apache.zookeeper.KeeperException.create(KeeperException.java:111)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.delete(ZooKeeper.java:873)
at com.fei.zk.ZkTest01.delete(ZkTest01.java:146)
at com.fei.zk.ZkTest01.main(ZkTest01.java:26)
删除不存在的节点时,会抛出异常。所以删除时最好先判断节点是否存在,同时删除时,节点的版本后也要正确。节点刚创建时,version=0,当对节点进行修改时,version会自增,这是为了避免并发是导致数据不一致.
1.5修改节点数据
public static void update(ZooKeeper zk){
try {
//创建临时节点/test-update,值为update
zk.create("/test-update", "update".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
//把值修改为update-2
zk.setData("/test-update", "update-2".getBytes("utf-8"), 0);
String data = new String(zk.getData("/test-update", false, null),"utf-8");
System.out.println("/test-update的值为"+data);
} catch (Exception e) {
e.printStackTrace();
}
zk connection OK....
zk connection OK,lock release....
/test-update的值为update-2
1.6 带有权限控制的节点
如果创建某个节点的客户端zk连接进行了权限认证并且创建节点时使用Ids.CREATOR_ALL_ACL,则其他客户端连接(没认证或认证信息不一致)是无法对这个节点进行读取、修改、删除等操作的。
public static void auth(){
try {
String connStr = "192.168.0.219:2181";
//创建zk1
ZooKeeper zk1 = new ZooKeeper(connStr,5000,null);
//zk1进行认证
zk1.addAuthInfo("digest", "jianfei".getBytes("utf-8"));
//zk1创建节点/fei,CREATOR_ALL_ACL进行权限控制
zk1.create("/fei", "fei".getBytes("utf-8"), Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
//创建zk2
ZooKeeper zk2 = new ZooKeeper(connStr,5000,null);
//zk2读取节点
System.out.println(new String(zk2.getData("/fei", false, null),"utf-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /fei
at org.apache.zookeeper.KeeperException.create(KeeperException.java:113)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.getData(ZooKeeper.java:1212)
at org.apache.zookeeper.ZooKeeper.getData(ZooKeeper.java:1241)
at com.fei.zk.ZkTest01.auth(ZkTest01.java:183)
at com.fei.zk.ZkTest01.main(ZkTest01.java:28)
看到报NoAuth for /fei的错误.
1.7获取子节点
/**
* 获取子节点
* @param zk
*/
public static void getChildrenNode(ZooKeeper zk){
try {
//创建一些临时节点
//注意不能再临时节点下创建子节点
zk.create("/app", "app".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//子节点可以是临时的
zk.create("/app/app01", "app01".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
zk.create("/app/app02", "app02".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
List<String> list = zk.getChildren("/app", false);
System.out.println("/app的子节点有:");
for(String c : list){
System.out.println(c);
}
} catch (Exception e) {
e.printStackTrace();
}
}
zk connection OK....
zk connection OK,lock release....
/app的子节点有:
app02
app01
1.8节点监控
其实上面getZk()方法里,就有对创建zk时连接的监控了。看ZooKeeper的方法,会发现其实在getData,isExist,getChildrenNode等方法上,都有Watch参数。
注意:节点上的监听,都是一次性的,比如getData上监听/test节点,然后修改/test的值,这时服务器会发送通知给客户端,但是第二次修改/test上的值时,服务器是不会再发通知给客户端的,除非客户端获取到第一次通知时,再次进行注册监听。
public static void watch(ZooKeeper zk){
try {
//监听/watch节点是否存在,
Stat stat = zk.exists("/watch", new Watcher(){
public void process(WatchedEvent event) {
if(event.getType() == EventType.NodeCreated){
System.out.println("exists中监听到..."+event.getPath()+"...创建了");
}else if(event.getType() == EventType.NodeDeleted){
System.out.println("exists中监听到..."+event.getPath()+"...删除了");
}else if(event.getType() == EventType.NodeDataChanged){
System.out.println("exists中监听到..."+event.getPath()+"...修改了");
}
}
});
if(stat == null){
System.out.println("/watch 不存在");
//创建
String s = zk.create("/watch", "watch".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println("创建/watch成功..." + s);
//获取,并监听
zk.getData("/watch", new Watcher(){
public void process(WatchedEvent event) {
if(event.getType() == EventType.NodeCreated){
System.out.println("getData中监听到..."+event.getPath()+"...创建了");
}else if(event.getType() == EventType.NodeDeleted){
System.out.println("getData中监听到..."+event.getPath()+"...删除了");
}else if(event.getType() == EventType.NodeDataChanged){
System.out.println("getData中监听到..."+event.getPath()+"...修改了");
}
}
}, null);
//修改2次
zk.setData("/watch", "watch2".getBytes("utf-8"), 0);
zk.setData("/watch", "watch3".getBytes("utf-8"), 1);
//删除
zk.delete("/watch", 2);
}else{
System.out.println("/watch 已存在");
}
} catch (Exception e) {
e.printStackTrace();
}
}
/watch 不存在
exists中监听到.../watch...创建了
创建/watch成功.../watch
getData中监听到.../watch...修改了
看到,就创建和第一次修改的时候,有通知,第二次修改和删除的时候没通知。。。。也许代码这直接写不直观,可以先写exists的监听,然后再服务器上打开./zkCli.sh,然后手动创建节点,修改值等操作,会更加直观对比。
节点上的监听都是一次性的,如果想一直监听,则需要接收到第一次通知后,再注册一次监听
1.9原生API的一些方便之处
通过上面的例子可以看出原生api的一些不方便之处
1)如果父节点不存在,不能直接创建子节点
2)删除时,如果节点不存在,抛出错误
3)节点监控是一次性的,要一直监控,得自己处理
4)连接丢失,需自己处理,比如重连接
以上是通过上面例子暂时看到的问题