1、ZkClient簡介
ZkClient是由Datameer的工程師開發(fā)的開源客戶端,對Zookeeper的原生API進行了包裝,實現了超時重連、Watcher反復注冊等功能;目前已經應用到了很多項目中,比如Dubbo、Kafka、Helix;
Github:https://github.com/sgroschupf/zkclient
Maven依賴
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version></version>
</dependency>
或者
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version></version>
</dependency>
2、ZkClient組件
IZKConnection:是一個ZkClient與Zookeeper之間的一個適配器;在代碼里直接使用的是ZKClient,實質上還是委托了zookeeper來處理了。
在ZKClient中,根據事件類型,分為
- 節(jié)點事件(數據事件),對應的事件處理器是IZKDataListener;
- 子節(jié)點事件,對應的事件處理器是IZKChildListener;
- Session事件,對應的事件處理器是IZKStatusListener;
ZkEventThread:是專門用來處理事件的線程
3、API介紹
- 啟動ZKClient:在創(chuàng)建ZKClient對象時,就完成了到ZooKeeper服務器連接的建立
1、啟動時,制定好connection string,連接超時時間,序列化工具等
2、創(chuàng)建并啟動eventThread,用于接收事件,并調度事件監(jiān)聽器Listener的執(zhí)行
3、連接到Zookeeper服務器,同時將ZKClient自身作為默認的Watcher
- 為節(jié)點注冊Watcher
Zookeeper 原始API的三個方法:getData,getChildren、exists,ZKClient都提供了相應的代理方法,比如exists,
hasListeners是看有沒有與該數據節(jié)點綁定的listener
所以,默認情況下,都會自動的為指定的path注冊watcher,并且是默認的watcher(ZKClient),那么怎樣才能讓hasListeners值為true呢,也就是怎么才能為path綁定Listener呢?
ZKClient提供了訂閱功能,一個新建的會話,只需要在取得響應的數據節(jié)點后,調用subscribeXXX就可以訂閱上相應的事件了。
- Zookeeper的CURD(節(jié)點的增刪查改)
Zookeeper中提供的變更操作有:節(jié)點的創(chuàng)建、刪除,節(jié)點數據的修改
1、創(chuàng)建操作,節(jié)點分為4種,所以ZKClient分別為他們提供了相應的代理
2、刪除節(jié)點操作
3、修改節(jié)點數據
updateDataSerialized:修改已系列化的數據;執(zhí)行過程是,先讀取數據,然后DataUpdater對數據修改,最后調用writeData將修改后的數據發(fā)送給服務端;
writeDataReturnStat:寫數據并返回數據的狀態(tài);
4、客戶端處理變更流程
ZKClient是默認的Watcher(ZKClient實現了Watcher接口),并且在為各個數據節(jié)點注冊的Watcher都是這個默認的Watcher,那么該如何將各種事件通知給相應的Listener呢:
1、判斷變更類型,變更類型分為state變更、ChildNode變更、NodeData變更;
2、取出與path關聯的Listeners,并為每一個Listener創(chuàng)建一個ZKEvent,將ZkEvent,將ZkEvent交給ZkEventThread處理;
3、ZkEventThread線程,拿到ZkEvent后,只需要調用ZkEvent的run方法進行處理就可以了,所以,具體的如何調用Listener,還要依賴于ZkEvent的run()實現
5、序列化處理
Zookeeper中,會涉及到序列化、反序列化的操作有兩種:getData/setData;在ZkClient中,分別用readData/WriteData來替代了。
- ReadData:先調用zookeeper的getData,然后使用ZKSerializer進行反序列化工作
- WriteData:先使用ZKSerializer將對象序列化后,再調用zookeeper的setData
6、注冊監(jiān)聽
在ZkClient中客戶端可以通過注冊相關的事件監(jiān)聽來實現對Zookeeper服務端事件的訂閱。
7、demo
package com.xxx.api.zkclient;
import com.xxx.ZookeeperUtil;
import com.xxx.api.natives.crud.User;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.ZkConnection;
import org.I0Itec.zkclient.serialize.ZkSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class ZkClientCrud<T> {
ZkClient zkClient ;
final static Logger logger = LoggerFactory.getLogger(ZkClientCrud.class);
public ZkClientCrud(ZkSerializer zkSerializer) {
logger.info("鏈接zk開始");
// zkClient=new ZkClient(ZookeeperUtil.connectString,ZookeeperUtil.sessionTimeout);
zkClient=new ZkClient(ZookeeperUtil.connectString,ZookeeperUtil.sessionTimeout,ZookeeperUtil.sessionTimeout,zkSerializer);
}
public void createEphemeral(String path,Object data){
zkClient.createEphemeral(path,data);
}
/***
* 支持創(chuàng)建遞歸方式
* @param path
* @param createParents
*/
public void createPersistent(String path,boolean createParents){
zkClient.createPersistent(path,createParents);
}
/***
* 創(chuàng)建節(jié)點 跟上data數據
* @param path
* @param data
*/
public void createPersistent(String path,Object data){
zkClient.createPersistent(path,data);
}
/***
* 子節(jié)點
* @param path
* @return
*/
public List<String> getChildren(String path){
return zkClient.getChildren(path);
}
public T readData(String path){
return zkClient.readData(path);
}
public void writeData(String path,Object data){
zkClient.writeData(path,data);
}
//遞歸刪除
public void deleteRecursive(String path){
zkClient.deleteRecursive(path);
}
}
package com.xxx.api.zkclient;
import com.xxx.api.natives.crud.User;
import org.I0Itec.zkclient.serialize.SerializableSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class ZkClientCrudTest {
final static Logger logger = LoggerFactory.getLogger(ZkClientCrudTest.class);
public static void main(String[] args) {
ZkClientCrud<User> zkClientCrud=new ZkClientCrud<User>(new SerializableSerializer());
String path="/root";
zkClientCrud.deleteRecursive(path);
zkClientCrud.createPersistent(path,"hi");
/* zkClientCrud.createPersistent(path+"/a/b/c",true);//遞歸創(chuàng)建 但是不能設在value
//zkClientCrud.createPersistent(path,"hi");
logger.info(zkClientCrud.readData(path));
//更新
zkClientCrud.writeData(path,"hello");
logger.info(zkClientCrud.readData(path));
logger.info(String.valueOf(zkClientCrud.getChildren(path)));
//子節(jié)點
List<String> list=zkClientCrud.getChildren(path);
for(String child:list){
logger.info("子節(jié)點:"+child);
}*/
User user=new User();
user.setUserid(1);
user.setUserName("張三");
zkClientCrud.writeData(path,user);
System.out.println(zkClientCrud.readData(path).getUserName());;
}
}
package com.xxx.api.zkclient;
import com.xxx.ZookeeperUtil;
import org.I0Itec.zkclient.*;
import org.apache.zookeeper.Watcher;
import java.util.List;
public class ZkClientWatcher {
ZkClient zkClient;
public ZkClientWatcher() {
zkClient= new ZkClient(new ZkConnection(ZookeeperUtil.connectString), ZookeeperUtil.sessionTimeout);
}
public void createPersistent(String path,Object data){
zkClient.createPersistent(path,data);
}
public void writeData(String path,Object object){
zkClient.writeData(path,object);
}
public void delete(String path){
zkClient.delete(path);
}
public boolean exists(String path){
return zkClient.exists(path);
}
public void deleteRecursive(String path){
zkClient.deleteRecursive(path);
}
//對父節(jié)點添加監(jiān)聽數據變化。
public void subscribe(String path){
zkClient.subscribeDataChanges(path, new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
System.out.printf("變更的節(jié)點為:%s,數據:%s\r\n", dataPath,data );
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
System.out.printf("刪除的節(jié)點為:%s\r\n", dataPath );
}
});
}
//對父節(jié)點添加監(jiān)聽子節(jié)點變化。
public void subscribe2(String path){
zkClient.subscribeChildChanges(path, new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
System.out.println("父節(jié)點: " + parentPath+",子節(jié)點:"+currentChilds+"\r\n");
}
});
}
//客戶端狀態(tài)
public void subscribe3(String path) {
zkClient.subscribeStateChanges(new IZkStateListener() {
@Override
public void handleStateChanged(Watcher.Event.KeeperState state) throws Exception {
if(state== Watcher.Event.KeeperState.SyncConnected){
//當我重新啟動后start,監(jiān)聽觸發(fā)
System.out.println("連接成功");
}else if(state== Watcher.Event.KeeperState.Disconnected){
System.out.println("連接斷開");//當我在服務端將zk服務stop時,監(jiān)聽觸發(fā)
}else
System.out.println("其他狀態(tài)"+state);
}
@Override
public void handleNewSession() throws Exception {
System.out.println("重建session");
}
@Override
public void handleSessionEstablishmentError(Throwable error) throws Exception {
}
});
}
/* @Override
public void handleDataChange(String dataPath, Object data) throws Exception {
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
}*/
}
package com.xxx.api.zkclient;
import java.util.concurrent.TimeUnit;
public class ZkClientWatcherTest {
public static void main(String[] args) throws InterruptedException {
ZkClientWatcher zkClientWatche=new ZkClientWatcher();
String path="/root";
zkClientWatche.deleteRecursive(path);
zkClientWatche.createPersistent(path,"hello");
zkClientWatche.subscribe(path);
zkClientWatche.subscribe2(path);
// zkClientWatche.subscribe3(path);//需要啟服務
// Thread.sleep(Integer.MAX_VALUE);
zkClientWatche.createPersistent(path+"/root2","word");
TimeUnit.SECONDS.sleep(1);
zkClientWatche.writeData(path,"hi");
TimeUnit.SECONDS.sleep(1);
//zkClientWatche.delete(path);//如果目錄下有內容 不能刪除 會報 Directory not empty for /root的異常
zkClientWatche.deleteRecursive(path);
TimeUnit.SECONDS.sleep(1); //這個main線程就結束
}
}
package com.xxx;
public class ZookeeperUtil {
/** zookeeper服務器地址 */
public static final String connectString = "192.168.0.101:2181,192.168.0.102:2181,192.168.0.104:2181";
/** 定義session失效時間 */
public static final int sessionTimeout = 5000;
public static final String path = "/root";
}