zookeeper原生增删改查的使用
1. 会话连接与恢复
- zookeeper连接相关API
- 创建连接关键方法public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,boolean canBeReadOnly)
- 客户端和zk服务端链接是一个异步的过程,当连接成功后后,客户端会收的一个watch通知
- 参数:
- connectString:连接服务器的ip字符串,比如: "192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181"可以是一个ip,也可以是多个ip,一个ip代表单机,多个ip代表集群也可以在ip后加路径
- sessionTimeout:超时时间,心跳收不到了,那就超时
- watcher:通知事件,如果有对应的事件触发,则会收到一个通知;如果不需要,那就设置为null
- canBeReadOnly:可读,当这个物理机节点断开后,还是可以读到数据的,只是不能写,此时数据被读取到的可能是旧数据,此处建议设置为false,不推荐使用
- sessionId:会话的id
- sessionPasswd:会话密码,当会话丢失后,可以依据 sessionId 和 sessionPasswd 重新获取会话
- zookeeper的连接操作
之前学习了命令连接zk,现在我们来用Java代码实现对zk的连接
/**
* @Date: 19-1-28
* @version: V1.0
* @Author: Chandler
* @Description: ${todo}
*/
@Slf4j
public class ZKConnect implements Watcher {
public static final String zkServerPath = "127.0.0.1:2181";
public static final Integer timeout = 5000;
public static void main(String[] args) throws IOException, InterruptedException {
ZooKeeper zk = new ZooKeeper(zkServerPath,timeout,new ZKConnect());
log.warn("客户端开始连接zookeeper服务器...");
log.warn("连接状态:{}",zk.getState());
new Thread().sleep(2000);
log.warn("连接状态:{}",zk.getState());
}
@Override
public void process(WatchedEvent watchedEvent) {
log.warn("接收到watch通知:{}",watchedEvent);
}
}
运行后结果:
2019-01-28 11:18:08,046 [main] [com.chandler.NativeDemo.zk.ZKConnect.main(ZKConnect.java:27)] - [WARN] 客户端开始连接zookeeper服务器...
2019-01-28 11:18:08,048 [main] [com.chandler.NativeDemo.zk.ZKConnect.main(ZKConnect.java:28)] - [WARN] 连接状态:CONNECTING
2019-01-28 11:18:08,049 [main-SendThread(127.0.0.1:2181)] [org.apache.zookeeper.ClientCnxn$SendThread.logStartConnect(ClientCnxn.java:1035)] - [INFO] Opening socket connection to server 127.0.0.1/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
2019-01-28 11:18:08,116 [main-SendThread(127.0.0.1:2181)] [org.apache.zookeeper.ClientCnxn$SendThread.primeConnection(ClientCnxn.java:877)] - [INFO] Socket connection established to 127.0.0.1/127.0.0.1:2181, initiating session
2019-01-28 11:18:08,145 [main-SendThread(127.0.0.1:2181)] [org.apache.zookeeper.ClientCnxn$SendThread.onConnected(ClientCnxn.java:1302)] - [INFO] Session establishment complete on server 127.0.0.1/127.0.0.1:2181, sessionid = 0x168922017280009, negotiated timeout = 5000
2019-01-28 11:18:08,147 [main-EventThread] [com.chandler.NativeDemo.zk.ZKConnect.process(ZKConnect.java:38)] - [WARN] 接收到watch通知:WatchedEvent state:SyncConnected type:None path:null
2019-01-28 11:18:10,048 [main] [com.chandler.NativeDemo.zk.ZKConnect.main(ZKConnect.java:32)] - [WARN] 连接状态:CONNECTED
通过我们的日志可以看到它的输出信息和我们使用终端命令登陆大致是一样的,都会提示我们登陆的SessionId、Timeout时间等等。
但是如果我们的连接断开了,如何重连呢?原生的zk重连机制不是很友好,具体重连操作见下方代码:
- zookeeper的会话重连接操作
/**
* @Date: 19-1-28
* @version: V1.0
* @Author: Chandler
* @Description: ${todo}
*/
@Slf4j
public class ZKConnectSessionWatcher implements Watcher {
public static final String zkServerPath = "127.0.0.1:2181";
public static final Integer timeout = 5000;
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper(zkServerPath, timeout, new ZKConnectSessionWatcher());
long sessionId = zk.getSessionId();
String ssid = "0x" + Long.toHexString(sessionId);
System.out.println(ssid);
byte[] sessionPassword = zk.getSessionPasswd();
log.warn("客户端开始连接zookeeper服务器...");
log.warn("连接状态:{}", zk.getState());
new Thread().sleep(1000);
log.warn("连接状态:{}", zk.getState());
new Thread().sleep(200);
// 开始会话重连
log.warn("开始会话重连...");
ZooKeeper zkSession = new ZooKeeper(zkServerPath,
timeout,
new ZKConnectSessionWatcher(),
sessionId,
sessionPassword);
log.warn("重新连接状态zkSession:{}", zkSession.getState());
new Thread().sleep(1000);
log.warn("重新连接状态zkSession:{}", zkSession.getState());
}
@Override
public void process(WatchedEvent watchedEvent) {
log.warn("接受到watch通知:{}", watchedEvent);
}
}
我们在这边手动模拟了zk会话重新连接的过程,在第一次的时候记录下SessionId,SessionPasswd,后面重新创建连接是传入这2个参数就能是会话重新连接。
输出的结果:
2019-01-28 11:28:01,549 [main] [com.chandler.NativeDemo.zk.ZKConnectSessionWatcher.main(ZKConnectSessionWatcher.java:29)] - [WARN] 客户端开始连接zookeeper服务器...
2019-01-28 11:28:01,554 [main-SendThread(127.0.0.1:2181)] [org.apache.zookeeper.ClientCnxn$SendThread.logStartConnect(ClientCnxn.java:1035)] - [INFO] Opening socket connection to server 127.0.0.1/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
2019-01-28 11:28:01,562 [main] [com.chandler.NativeDemo.zk.ZKConnectSessionWatcher.main(ZKConnectSessionWatcher.java:30)] - [WARN] 连接状态:CONNECTING
2019-01-28 11:28:01,622 [main-SendThread(127.0.0.1:2181)] [org.apache.zookeeper.ClientCnxn$SendThread.primeConnection(ClientCnxn.java:877)] - [INFO] Socket connection established to 127.0.0.1/127.0.0.1:2181, initiating session
2019-01-28 11:28:01,646 [main-SendThread(127.0.0.1:2181)] [org.apache.zookeeper.ClientCnxn$SendThread.onConnected(ClientCnxn.java:1302)] - [INFO] Session establishment complete on server 127.0.0.1/127.0.0.1:2181, sessionid = 0x16892201728000b, negotiated timeout = 5000
2019-01-28 11:28:01,647 [main-EventThread] [com.chandler.NativeDemo.zk.ZKConnectSessionWatcher.process(ZKConnectSessionWatcher.java:51)] - [WARN] 接受到watch通知:WatchedEvent state:SyncConnected type:None path:null
2019-01-28 11:28:02,562 [main] [com.chandler.NativeDemo.zk.ZKConnectSessionWatcher.main(ZKConnectSessionWatcher.java:32)] - [WARN] 连接状态:CONNECTED
2019-01-28 11:28:02,763 [main] [com.chandler.NativeDemo.zk.ZKConnectSessionWatcher.main(ZKConnectSessionWatcher.java:37)] - [WARN] 开始会话重连...
2019-01-28 11:28:02,764 [main] [org.apache.zookeeper.ZooKeeper.<init>(ZooKeeper.java:578)] - [INFO] Initiating client connection, connectString=127.0.0.1:2181 sessionTimeout=5000 watcher=com.chandler.NativeDemo.zk.ZKConnectSessionWatcher@123a439b sessionId=0 sessionPasswd=<hidden>
2019-01-28 11:28:02,765 [main] [com.chandler.NativeDemo.zk.ZKConnectSessionWatcher.main(ZKConnectSessionWatcher.java:44)] - [WARN] 重新连接状态zkSession:CONNECTING
2019-01-28 11:28:02,766 [main-SendThread(127.0.0.1:2181)] [org.apache.zookeeper.ClientCnxn$SendThread.logStartConnect(ClientCnxn.java:1035)] - [INFO] Opening socket connection to server 127.0.0.1/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
2019-01-28 11:28:02,767 [main-SendThread(127.0.0.1:2181)] [org.apache.zookeeper.ClientCnxn$SendThread.primeConnection(ClientCnxn.java:877)] - [INFO] Socket connection established to 127.0.0.1/127.0.0.1:2181, initiating session
2019-01-28 11:28:02,852 [main-SendThread(127.0.0.1:2181)] [org.apache.zookeeper.ClientCnxn$SendThread.onConnected(ClientCnxn.java:1302)] - [INFO] Session establishment complete on server 127.0.0.1/127.0.0.1:2181, sessionid = 0x16892201728000c, negotiated timeout = 5000
2019-01-28 11:28:02,852 [main-EventThread] [com.chandler.NativeDemo.zk.ZKConnectSessionWatcher.process(ZKConnectSessionWatcher.java:51)] - [WARN] 接受到watch通知:WatchedEvent state:SyncConnected type:None path:null
2019-01-28 11:28:03,766 [main] [com.chandler.NativeDemo.zk.ZKConnectSessionWatcher.main(ZKConnectSessionWatcher.java:46)] - [WARN] 重新连接状态zkSession:CONNECTED
2. 增加结点
- 同步或者异步创建节点,都不支持子节点的递归创建,异步有一个callback函数
- 同步增加结点方法
主要核心方法是public String create(final String path, byte data[], List acl,CreateMode createMode) - 参数:
- path:创建的路径
- data:存储的数据的byte[]
- acl:控制权限策略
- Ids.OPEN_ACL_UNSAFE --> world:anyone:cdrwa
- CREATOR_ALL_ACL --> auth:user:password:cdrwa
- createMode:节点类型, 是一个枚举
- PERSISTENT:持久节点
- PERSISTENT_SEQUENTIAL:持久顺序节点
- EPHEMERAL:临时节点
- EPHEMERAL_SEQUENTIAL:临时顺序节点
/**
* @Date: 19-1-28
* @version: V1.0
* @Author: Chandler
* @Description: ${todo}
*/
public class ZKNodeOperator implements Watcher {
private ZooKeeper zooKeeper = null;
public static final String zkServerPath = "127.0.0.1:2181";
public static final Integer timeout = 5000;
public ZKNodeOperator() {
}
public ZKNodeOperator(String connectingString){
try {
zooKeeper = new ZooKeeper(connectingString,timeout,new ZKNodeOperator());
} catch (IOException e){
e.printStackTrace();
if (zooKeeper != null) {
try {
zooKeeper.close();
} catch (InterruptedException e1){
e1.printStackTrace();
}
}
}
}
/**
* 创建zk结点
*/
public void createZKNode(String path, byte[] data, List<ACL> acls){
String result = "";
try {
result = zooKeeper.create(path,data,acls, CreateMode.PERSISTENT);
System.out.println("创建节点:\t" + result + "\t成功...");
new Thread().sleep(2000);
} catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
ZKNodeOperator zkServer = new ZKNodeOperator(zkServerPath);
zkServer.createZKNode("/testnode", "testnode".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE);
}
@Override
public void process(WatchedEvent event) {
}
运行后我们在终端查看结果:
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper, test, testnode]
[zk: localhost:2181(CONNECTED) 1] get /test
test testnode
[zk: localhost:2181(CONNECTED) 1] get /testnode
testnode
cZxid = 0x49
ctime = Mon Jan 28 13:46:32 CST 2019
mZxid = 0x49
mtime = Mon Jan 28 13:46:32 CST 2019
pZxid = 0x49
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 0
testnode 结点已经被创建出来了,大功告成~
下面我们试试异步创建结点操作
- 异步创建结点
- 方法和参数都和同步一样,只是参数需要多传入一个StringCallback回调函数。
- 先创建增加节点的回调函数类
public class CreateCallBack implements AsyncCallback.StringCallback {
@Override
public void processResult(int rc, String path, Object ctx, String name) {
System.out.println("创建节点: " + path);
System.out.println((String)ctx);
}
}
- 稍微修改一下创建节点方法
/**
* 创建zk结点
*/
public void createZKNode(String path, byte[] data, List<ACL> acls){
String result = "";
try {
//同步操作
//result = zooKeeper.create(path,data,acls, CreateMode.PERSISTENT);
//异步操作
String ctx = "{'create':'success'}";
zooKeeper.create(path, data, acls, CreateMode.PERSISTENT, new CreateCallBack(), ctx);
System.out.println("创建节点:\t" + result + "\t成功...");
new Thread().sleep(2000);
} catch (Exception e){
e.printStackTrace();
}
}
- 执行结果:
进入终端查看结果:
[zk: localhost:2181(CONNECTED) 4] ls /
[zookeeper, test, testnode, test-create-node]
[zk: localhost:2181(CONNECTED) 5] get /test-create-node
test-create-node
cZxid = 0x4c
ctime = Mon Jan 28 13:59:14 CST 2019
mZxid = 0x4c
mtime = Mon Jan 28 13:59:14 CST 2019
pZxid = 0x4c
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 16
numChildren = 0
创建成功后我们在控制台也能够看到我们在回调函数添加的日志信息:
创建节点: 成功...
2019-01-28 13:59:14,738 [main-SendThread(127.0.0.1:2181)] [org.apache.zookeeper.ClientCnxn$SendThread.primeConnection(ClientCnxn.java:877)] - [INFO] Socket connection established to 127.0.0.1/127.0.0.1:2181, initiating session
2019-01-28 13:59:14,768 [main-SendThread(127.0.0.1:2181)] [org.apache.zookeeper.ClientCnxn$SendThread.onConnected(ClientCnxn.java:1302)] - [INFO] Session establishment complete on server 127.0.0.1/127.0.0.1:2181, sessionid = 0x16892201728000e, negotiated timeout = 5000
创建节点: /test-create-node
{'create':'success'}
3. 删除节点
-
删除节点API
同步:public void delete(final String path, int version)throws InterruptedException, KeeperException
异步:public void delete(final String path, int version, VoidCallback cb,Object ctx) -
参数:
- path:要删除的节点的路径。
- version:预期的节点版本。
-
同步删除节点
我们直接添加删除相关方法后然后执行main方法
public static void main(String[] args) throws KeeperException, InterruptedException {
ZKNodeOperator zkServer = new ZKNodeOperator(zkServerPath);
zkServer.getZookeeper().delete("/test-create-node", 0);
Thread.sleep(2000);
}
运行后终端结果如下:我们之前添加的test-create-node节点已经被删除了~
[zk: localhost:2181(CONNECTED) 6] ls /
[zookeeper, test, testnode]
- 异步删除节点
- 先创建回调函数
public class DeleteCallBack implements AsyncCallback.VoidCallback {
@Override
public void processResult(int rc, String path, Object ctx) {
System.out.println("删除节点" + path);
System.out.println((String)ctx);
}
}
- 我们重新创建test-create-node节点,添加删除相关方法后然后执行main方法
public static void main(String[] args) throws KeeperException, InterruptedException {
ZKNodeOperator zkServer = new ZKNodeOperator(zkServerPath);
String ctx = "{'delete':'success'}";
zkServer.getZookeeper().delete("/test-create-node", 0, new DeleteCallBack(), ctx);
Thread.sleep(2000);
}
控制台输出:
删除节点/test-create-node
{'delete':'success'}
终端查看节点也已经被删除。
4. 修改节点
- 修改节点API
- 同步:public Stat setData(final String path, byte data[], int version)throws KeeperException, InterruptedException
- 异步:public void setData(final String path, byte data[], int version,StatCallback cb, Object ctx)
- 同步能够返回当前的节点状态。
- 参数
- path:节点路径
- data:数据
- version:预期的匹配版本(每次修改会自动累加)
- 同步删除节点操作
- 我们修改一下main方法
public static void main(String[] args) throws KeeperException, InterruptedException {
ZKNodeOperator zkServer = new ZKNodeOperator(zkServerPath);
Stat status = zkServer.getZookeeper().setData("/testnode", "xyz".getBytes(), 0);
System.out.println(status.getVersion());
Thread.sleep(2000);
}
终端查询节点信息:
[zk: localhost:2181(CONNECTED) 11] get /testnode
xyz
cZxid = 0x49
ctime = Mon Jan 28 13:46:32 CST 2019
mZxid = 0x5e
mtime = Mon Jan 28 15:11:40 CST 2019
pZxid = 0x49
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0
dataVersion已经累加为1
每次修改version必须是当前的版本号,否则抛BadVersionException异常:
org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for /testnode
at org.apache.zookeeper.KeeperException.create(KeeperException.java:118)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:54)
at org.apache.zookeeper.ZooKeeper.setData(ZooKeeper.java:1330)
- 异步修改节点操作
- 创建修改节点的回调函数
public class SetCallback implements AsyncCallback.StatCallback {
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
System.out.println("修改节点" + path);
System.out.println((String)ctx);
}
}
- 修改main方法中修改节点参数
public static void main(String[] args) throws KeeperException, InterruptedException {
ZKNodeOperator zkServer = new ZKNodeOperator(zkServerPath);
zkServer.getZookeeper().setData("/testnode", "chandler SetCallback".getBytes(), 2,new SetCallback(),ctx);
System.out.println(status.getVersion());
Thread.sleep(2000);
}
- 最终结果:
回调函数执行:
修改节点/testnode
{'set':'success'}
终端查看:
[zk: localhost:2181(CONNECTED) 14] get /testnode
chandler SetCallback
cZxid = 0x49
ctime = Mon Jan 28 13:46:32 CST 2019
mZxid = 0x6a
mtime = Mon Jan 28 15:37:43 CST 2019
pZxid = 0x49
cversion = 0
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 20
numChildren = 0
5. 查询节点
- 查询API
//同步
public byte[] getData(final String path, Watcher watcher, Stat stat)
throws KeeperException, InterruptedException
public byte[] getData(String path, boolean watch, Stat stat)
throws KeeperException, InterruptedException
//异步
public void getData(final String path, Watcher watcher,DataCallback cb, Object ctx)
public void getData(String path, boolean watch, DataCallback cb, Object ctx)
- 参数:
- path:节点路径
- watch:自定义watcher/true或者false,注册一个watch事件
- stat:节点的数据和统计信息
- DataCallback:用于检索节点的数据和统计信息的回调函数
- 同步查询操作
public static void main(String[] args) throws KeeperException, InterruptedException {
ZKNodeOperator zkServer = new ZKNodeOperator(zkServerPath);
Stat stat = new Stat();
byte[] resByte = zkServer.getZookeeper().getData("/testnode", true, stat);
String result = new String(resByte);
System.out.println("当前值:" + result);
}
- 结果:
控制台的日志输出当前值:chandler SetCallback与终端查询结果一致~
[zk: localhost:2181(CONNECTED) 15] get /testnode
chandler SetCallback
cZxid = 0x49
ctime = Mon Jan 28 13:46:32 CST 2019
mZxid = 0x6a
mtime = Mon Jan 28 15:37:43 CST 2019
pZxid = 0x49
cversion = 0
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 20
numChildren = 0
- 异步查询操作
- 创建异步查询回调函数,实现DataCallback接口
public class GetCallBack implements AsyncCallback.DataCallback {
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
System.out.println("查询节点" + path);
System.out.println((String)ctx);
}
}
- 修改main方法后执行
public static void main(String[] args) throws KeeperException, InterruptedException {
ZKNodeOperator zkServer = new ZKNodeOperator(zkServerPath);
Stat stat = new Stat();
String ctx = "{'get':'success'}";
zkServer.getZookeeper().getData("/testnode", true,new GetCallBack(),ctx);
Thread.sleep(2000);
}
- 结果:
回调函数的方法被触发
查询节点/testnode
{'get':'success'}
6. 总结
- 原生的zookeeper api 不支持会话自动重连。
- 异步增删改查的回调时,都需要实现不同的回调函数,但是他们都是继承AsyncCallback。