Zookeeper基礎(三):Zookeeper客戶端ZkClient

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組件

image.png

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
image.png
  • 為節(jié)點注冊Watcher
    Zookeeper 原始API的三個方法:getData,getChildren、exists,ZKClient都提供了相應的代理方法,比如exists,
image.png

hasListeners是看有沒有與該數據節(jié)點綁定的listener


image.png

所以,默認情況下,都會自動的為指定的path注冊watcher,并且是默認的watcher(ZKClient),那么怎樣才能讓hasListeners值為true呢,也就是怎么才能為path綁定Listener呢?
ZKClient提供了訂閱功能,一個新建的會話,只需要在取得響應的數據節(jié)點后,調用subscribeXXX就可以訂閱上相應的事件了。


image.png
  • Zookeeper的CURD(節(jié)點的增刪查改)
    Zookeeper中提供的變更操作有:節(jié)點的創(chuàng)建、刪除,節(jié)點數據的修改

1、創(chuàng)建操作,節(jié)點分為4種,所以ZKClient分別為他們提供了相應的代理


image.png

2、刪除節(jié)點操作


image.png

3、修改節(jié)點數據


image.png

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服務端事件的訂閱。


image.png

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";
}



最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • ... 一、相關概念 中間件:為分布式系統(tǒng)提供協調服務的組件,如專門用于計算服務的機器就是一個計算型中間件,還有專...
    帥可兒妞閱讀 493評論 0 0
  • 轉自:http://www.lxweimin.com/p/84ad63127cd1作者:Jeffbond 簡介 Z...
    大數據Zone閱讀 941評論 0 8
  • Zookeeper系列文章1.Zookeeper簡介2.Zookeeper集群安裝3.原生API操作Zookeep...
    deve_雨軒閱讀 578評論 0 0
  • 時常聽到心靈雞湯:開心一天是過,不開心一天也是過,為何不開開心心度過每一天呢?簡單樸素的話語直抵心間,可似乎總有些...
    Christy_22ba閱讀 164評論 0 0
  • 我喜歡黑夜的顏色 我更喜歡在夜深人靜的時候 用我的雙眼 去看清這夜晚的城市 但我什么也看不見 此刻,我想到了鬼 只...
    柳四公子閱讀 371評論 0 0