zookeeper的watch(原生API)

github: https://github.com/zhaikaishun/zookeeper_tutorial


Zookeeper的watcher事件

zookeeper有watch事件,是一次性觸發的,當watch監視的數據發生變化時,通知設置了該watch的client,即watcher。
同樣,其watcher是監聽數據發生了某些變化,那就一定會有對應的事件類型,和狀態類型。
事件類型(znode節點相關的)
- EventType.NodeCreated
- EventType.NodeDataChanged
- EventType.NodeChildrenChanged
- EventType.NodeDeleted
狀態類型:(是跟客戶端實例相關的,簡單的說就是客戶端和服務器端連接狀態相關的)
- KeeperState.Disconnected
- KeeperState.SyncConnected
- KeeperState.AuthFailed 認證失敗
- KeeperState.Expired 過期

watcher和watch

簡單的說,一個節點上的某個程序監控某個節點,那麼這個節點上的這個程序就是一個watcher,而監聽的這個事件(動作),就是一個watch。watch事件,是一次性觸發的,只能監聽一次,第二次對此節點的修改就監聽不到了,如果想一直監聽,大概有兩種方案,一種是在出發事件後執行方法的時候有個watch的參數再設置爲true,一種是這個時候再創建一個watch,這種是有點麻煩。
看一個例子吧:
最好是去github上下載下來自己運行一下。

  • 設置watcher, 我這裏每次create的時候,都設置了一下watcher,例如下面代碼中 this.zk.exists(path, ifsetTrue);
  • 需要實現implements Watcher接口以及重寫實現方法process,我這裏監聽比較簡單。也就不多說了。
  • 反正是要注意,如果只設置一次監聽,那麼監聽完之後,第二次就監聽不到了。若需要多次監聽,那麼最好是再監聽一次
/**
 * Zookeeper Wathcher 
 * 本類就是一個Watcher類(實現了org.apache.zookeeper.Watcher類)
 * @author(alienware)
 * @since 2015-6-14
 */
public class ZooKeeperWatcher implements Watcher {

    /** 定義原子變量 */
    AtomicInteger seq = new AtomicInteger();
    /** 定義session失效時間 */
    private static final int SESSION_TIMEOUT = 10000;
    /** zookeeper服務器地址 */
    private static final String CONNECTION_ADDR = "192.168.1.31:2181";
    /** zk父路徑設置 */
    private static final String PARENT_PATH = "/testWatch";
    /** zk子路徑設置 */
    private static final String CHILDREN_PATH = "/testWatch/children";
    /** 進入標識 */
    private static final String LOG_PREFIX_OF_MAIN = "【Main】";
    /** zk變量 */
    private ZooKeeper zk = null;
    /** 信號量設置,用於等待zookeeper連接建立之後 通知阻塞程序繼續向下執行 */
    private CountDownLatch connectedSemaphore = new CountDownLatch(1);

    /**
     * 創建ZK連接
     * @param connectAddr ZK服務器地址列表
     * @param sessionTimeout Session超時時間
     */
    public void createConnection(String connectAddr, int sessionTimeout) {
        this.releaseConnection();
        try {
            zk = new ZooKeeper(connectAddr, sessionTimeout, this);
            System.out.println(LOG_PREFIX_OF_MAIN + "開始連接ZK服務器");
            connectedSemaphore.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 關閉ZK連接
     */
    public void releaseConnection() {
        if (this.zk != null) {
            try {
                this.zk.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 創建節點
     * @param path 節點路徑
     * @param data 數據內容
     * @return 
     */
    public boolean createPath(String path, String data,boolean ifsetTrue) {
        try {
            //設置監控(由於zookeeper的監控都是一次性的所以 每次必須設置監控)
            this.zk.exists(path, ifsetTrue);
            System.out.println(LOG_PREFIX_OF_MAIN + "節點創建成功, Path: " + 
                               this.zk.create(  /**路徑*/ 
                                                path, 
                                                /**數據*/
                                                data.getBytes(), 
                                                /**所有可見*/
                                                Ids.OPEN_ACL_UNSAFE, 
                                                /**永久存儲*/
                                                CreateMode.PERSISTENT ) +   
                               ", content: " + data);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 讀取指定節點數據內容
     * @param path 節點路徑
     * @return
     */
    public String readData(String path, boolean needWatch) {
        try {
            return new String(this.zk.getData(path, needWatch, null));
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 更新指定節點數據內容
     * @param path 節點路徑
     * @param data 數據內容
     * @return
     */
    public boolean writeData(String path, String data) {
        try {
            System.out.println(LOG_PREFIX_OF_MAIN + "更新數據成功,path:" + path + ", stat: " +
                                this.zk.setData(path, data.getBytes(), -1));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 刪除指定節點
     * 
     * @param path
     *            節點path
     */
    public void deleteNode(String path) {
        try {
            this.zk.delete(path, -1);
            System.out.println(LOG_PREFIX_OF_MAIN + "刪除節點成功,path:" + path);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 判斷指定節點是否存在
     * @param path 節點路徑
     */
    public Stat exists(String path, boolean needWatch) {
        try {
            return this.zk.exists(path, needWatch);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 獲取子節點
     * @param path 節點路徑
     */
    private List<String> getChildren(String path, boolean needWatch) {
        try {
            return this.zk.getChildren(path, needWatch);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 刪除所有節點
     */
    public void deleteAllTestPath() {
        if(this.exists(CHILDREN_PATH, false) != null){
            this.deleteNode(CHILDREN_PATH);
        }
        if(this.exists(PARENT_PATH, false) != null){
            this.deleteNode(PARENT_PATH);
        }       
    }

    /**
     * 收到來自Server的Watcher通知後的處理。
     */
    @Override
    public void process(WatchedEvent event) {

        System.out.println("進入 process 。。。。。event = " + event);

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (event == null) {
            return;
        }

        // 連接狀態
        KeeperState keeperState = event.getState();
        // 事件類型
        EventType eventType = event.getType();
        // 受影響的path
        String path = event.getPath();

        String logPrefix = "【Watcher-" + this.seq.incrementAndGet() + "】";

        System.out.println(logPrefix + "收到Watcher通知");
        System.out.println(logPrefix + "連接狀態:\t" + keeperState.toString());
        System.out.println(logPrefix + "事件類型:\t" + eventType.toString());

        if (KeeperState.SyncConnected == keeperState) {
            // 成功連接上ZK服務器
            if (EventType.None == eventType) {
                System.out.println(logPrefix + "成功連接上ZK服務器");
                connectedSemaphore.countDown();
            } 
            //創建節點
            else if (EventType.NodeCreated == eventType) {
                System.out.println(logPrefix + "節點創建");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.exists(path, true);
            } 
            //更新節點
            else if (EventType.NodeDataChanged == eventType) {
                System.out.println(logPrefix + "節點數據更新");
                System.out.println("我看看走不走這裏........");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(logPrefix + "數據內容: " + this.readData(PARENT_PATH, true));
            } 
            //更新子節點
            else if (EventType.NodeChildrenChanged == eventType) {
                System.out.println(logPrefix + "子節點變更");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(logPrefix + "子節點列表:" + this.getChildren(PARENT_PATH, true));
            } 
            //刪除節點
            else if (EventType.NodeDeleted == eventType) {
                System.out.println(logPrefix + "節點 " + path + " 被刪除");
            }
            else ;
        } 
        else if (KeeperState.Disconnected == keeperState) {
            System.out.println(logPrefix + "與ZK服務器斷開連接");
        } 
        else if (KeeperState.AuthFailed == keeperState) {
            System.out.println(logPrefix + "權限檢查失敗");
        } 
        else if (KeeperState.Expired == keeperState) {
            System.out.println(logPrefix + "會話失效");
        }
        else ;

        System.out.println("--------------------------------------------");

    }

    /**
     * <B>方法名稱:</B>測試zookeeper監控<BR>
     * <B>概要說明:</B>主要測試watch功能<BR>
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {

        //建立watcher
        ZooKeeperWatcher zkWatch = new ZooKeeperWatcher();
        //創建連接
        zkWatch.createConnection(CONNECTION_ADDR, SESSION_TIMEOUT);
        //System.out.println(zkWatch.zk.toString());

        Thread.sleep(1000);

        // 清理節點
        zkWatch.deleteAllTestPath();

        if (zkWatch.createPath(PARENT_PATH, System.currentTimeMillis() + "",true)) {

            Thread.sleep(1000);


            // 讀取數據
            System.out.println("---------------------- read parent ----------------------------");
            //zkWatch.readData(PARENT_PATH, true);

            // 讀取子節點
            System.out.println("---------------------- read children path ----------------------------");
            zkWatch.getChildren(PARENT_PATH, true);

            // 更新數據
            zkWatch.writeData(PARENT_PATH, System.currentTimeMillis() + "");

            Thread.sleep(1000);

            // 創建子節點
            zkWatch.createPath(CHILDREN_PATH, System.currentTimeMillis() + "",true);

            Thread.sleep(1000);

            zkWatch.writeData(CHILDREN_PATH, System.currentTimeMillis() + "");
        }

        Thread.sleep(50000);
        // 清理節點
        zkWatch.deleteAllTestPath();
        Thread.sleep(1000);
        zkWatch.releaseConnection();
    }

}

輸出就在這裏了,想具體瞭解的話,自己敲一下,然後覆蓋一下代碼。一個一個功能的執行,查看功能即可

【Main】開始連接ZK服務器
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:None path:null
【Watcher-1】收到Watcher通知
【Watcher-1】連接狀態:    SyncConnected
【Watcher-1】事件類型:    None
【Watcher-1】成功連接上ZK服務器
--------------------------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeCreated path:/testWatch
【Main】節點創建成功, Path: /testWatch, content: 1508077399368
【Watcher-2】收到Watcher通知
【Watcher-2】連接狀態:    SyncConnected
【Watcher-2】事件類型:    NodeCreated
【Watcher-2】節點創建
--------------------------------------------
---------------------- read parent ----------------------------
---------------------- read children path ----------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeDataChanged path:/testWatch
【Main】更新數據成功,path:/testWatch, stat: 30064771078,30064771079,1508071193606,1508071194762,1,0,0,0,13,0,30064771078

【Watcher-3】收到Watcher通知
【Watcher-3】連接狀態:    SyncConnected
【Watcher-3】事件類型:    NodeDataChanged
【Watcher-3】節點數據更新
我看看走不走這裏........
【Watcher-3】數據內容: 1508077400538
--------------------------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeCreated path:/testWatch/children
【Main】節點創建成功, Path: /testWatch/children, content: 1508077401581
【Watcher-4】收到Watcher通知
【Watcher-4】連接狀態:    SyncConnected
【Watcher-4】事件類型:    NodeCreated
【Watcher-4】節點創建
--------------------------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/testWatch
【Watcher-5】收到Watcher通知
【Watcher-5】連接狀態:    SyncConnected
【Watcher-5】事件類型:    NodeChildrenChanged
【Watcher-5】子節點變更
【Main】更新數據成功,path:/testWatch/children, stat: 30064771080,30064771081,1508071195777,1508071196784,1,0,0,0,13,0,30064771080

【Watcher-5】子節點列表:[children]
--------------------------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeDataChanged path:/testWatch/children
【Watcher-6】收到Watcher通知
【Watcher-6】連接狀態:    SyncConnected
【Watcher-6】事件類型:    NodeDataChanged
【Watcher-6】節點數據更新
我看看走不走這裏........
【Watcher-6】數據內容: 1508077400538
--------------------------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/testWatch
【Main】刪除節點成功,path:/testWatch/children
【Main】刪除節點成功,path:/testWatch
【Watcher-7】收到Watcher通知
【Watcher-7】連接狀態:    SyncConnected
【Watcher-7】事件類型:    NodeChildrenChanged
【Watcher-7】子節點變更

實際應用一個場景

我們希望zookeeper對分佈式系統的配置文件進行管理,也就是說多個服務器進行watcher,zookeeper節點發送變化,則我們實時更新配置文件。我們要完成多個應用服務器註冊watcher,然後實時觀察數據的變化,然後反饋給媒體服務器變更的數據信息,觀察zookeeper節點

下面是一個例子: 代碼在bjsxt.zookeeper.cluster中
本例子模擬多臺服務器同時監控一個節點。然後另外一個程序進行管理,所監控的這幾臺機器得到節點變更的通知。
本例中,Client1和Client2相當於兩臺服務器,共同watch一個節點。Test相當於管理者,用來管理這兩個客戶端的配置。
代碼如下,具體的需要自己下載下來進行調試
Client1

public class Client1 {

    public static void main(String[] args) throws Exception{

        ZKWatcher myWatcher = new ZKWatcher();
        Thread.sleep(100000000);
    }
}

Client2

public class Client2 {

    public static void main(String[] args) throws Exception{

        ZKWatcher myWatcher = new ZKWatcher();
        Thread.sleep(100000000);
    }
}

ZKWatcher

public class ZKWatcher implements Watcher {

    /** zk變量 */
    private ZooKeeper zk = null;

    /** 父節點path */
    static final String PARENT_PATH = "/super";

    /** 信號量設置,用於等待zookeeper連接建立之後 通知阻塞程序繼續向下執行 */
    private CountDownLatch connectedSemaphore = new CountDownLatch(1);

    private List<String> cowaList = new CopyOnWriteArrayList<String>();


    /** zookeeper服務器地址 */
    public static final String CONNECTION_ADDR = "192.168.1.31:2181,192.168.1.32:2181,192.168.1.33:2181";
    /** 定義session失效時間 */
    public static final int SESSION_TIMEOUT = 30000;

    public ZKWatcher() throws Exception{
        zk = new ZooKeeper(CONNECTION_ADDR, SESSION_TIMEOUT, this);
        System.out.println("開始連接ZK服務器");
        connectedSemaphore.await();
    }


    @Override
    public void process(WatchedEvent event) {
        // 連接狀態
        KeeperState keeperState = event.getState();
        // 事件類型
        EventType eventType = event.getType();
        // 受影響的path
        String path = event.getPath();
        System.out.println("受影響的path : " + path);


        if (KeeperState.SyncConnected == keeperState) {
            // 成功連接上ZK服務器
            if (EventType.None == eventType) {
                System.out.println("成功連接上ZK服務器");
                connectedSemaphore.countDown();
                try {
                    if(this.zk.exists(PARENT_PATH, false) == null){
                        this.zk.create(PARENT_PATH, "root".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);      
                    }
                    List<String> paths = this.zk.getChildren(PARENT_PATH, true);
                    for (String p : paths) {
                        System.out.println(p);
                        this.zk.exists(PARENT_PATH + "/" + p, true);
                    }
                } catch (KeeperException | InterruptedException e) {
                    e.printStackTrace();
                }       
            } 
            //創建節點
            else if (EventType.NodeCreated == eventType) {
                System.out.println("節點創建");
                try {
                    this.zk.exists(path, true);
                } catch (KeeperException | InterruptedException e) {
                    e.printStackTrace();
                }
            } 
            //更新節點
            else if (EventType.NodeDataChanged == eventType) {
                System.out.println("節點數據更新");
                try {
                    //update nodes  call function
                    this.zk.exists(path, true);
                } catch (KeeperException | InterruptedException e) {
                    e.printStackTrace();
                }
            } 
            //更新子節點
            else if (EventType.NodeChildrenChanged == eventType) {
                System.out.println("子節點 ... 變更");
                try {
                    List<String> paths = this.zk.getChildren(path, true);
                    if(paths.size() >= cowaList.size()){
                        paths.removeAll(cowaList);
                        for(String p : paths){
                            this.zk.exists(path + "/" + p, true);
                            //this.zk.getChildren(path + "/" + p, true);
                            System.out.println("這個是新增的子節點 : " + path + "/" + p);
                            //add new nodes  call function
                        }
                        cowaList.addAll(paths);
                    } else {
                        cowaList = paths;
                    }
                    System.out.println("cowaList: " + cowaList.toString());
                    System.out.println("paths: " + paths.toString());

                } catch (KeeperException | InterruptedException e) {
                    e.printStackTrace();
                }
            } 
            //刪除節點
            else if (EventType.NodeDeleted == eventType) {
                System.out.println("節點 " + path + " 被刪除");
                try {
                    //delete nodes  call function
                    this.zk.exists(path, true);
                } catch (KeeperException | InterruptedException e) {
                    e.printStackTrace();
                }
            }
            else ;
        } 
        else if (KeeperState.Disconnected == keeperState) {
            System.out.println("與ZK服務器斷開連接");
        } 
        else if (KeeperState.AuthFailed == keeperState) {
            System.out.println("權限檢查失敗");
        } 
        else if (KeeperState.Expired == keeperState) {
            System.out.println("會話失效");
        }
        else ;

        System.out.println("--------------------------------------------");
    }



}

Test

public class Test {


    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181,192.168.1.33:2181";
    /** session超時時間 */
    static final int SESSION_OUTTIME = 2000;//ms 
    /** 信號量,阻塞程序執行,用於等待zookeeper連接成功,發送成功信號 */
    static final CountDownLatch connectedSemaphore = new CountDownLatch(1);

    public static void main(String[] args) throws Exception{

        ZooKeeper zk = new ZooKeeper(CONNECT_ADDR, SESSION_OUTTIME, new Watcher(){
            @Override
            public void process(WatchedEvent event) {
                //獲取事件的狀態
                KeeperState keeperState = event.getState();
                EventType eventType = event.getType();
                //如果是建立連接
                if(KeeperState.SyncConnected == keeperState){
                    if(EventType.None == eventType){
                        //如果建立連接成功,則發送信號量,讓後續阻塞程序向下執行
                        connectedSemaphore.countDown();
                        System.out.println("zk 建立連接");
                    }
                }
            }
        });

        //進行阻塞
        connectedSemaphore.await();

//      //創建子節點
//      zk.create("/super/c1", "c1".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        //創建子節點
//      zk.create("/super/c2", "c2".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        //創建子節點
        String result = zk.create("/super/c3", "c3".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        System.out.println(result);
        //創建子節點
//      zk.create("/super/c4", "c4".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

//      zk.create("/super/c4/c44", "c44".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

        //獲取節點信息
//      byte[] data = zk.getData("/testRoot", false, null);
//      System.out.println(new String(data));
//      System.out.println(zk.getChildren("/testRoot", false));

        //修改節點的值
//      zk.setData("/super/c1", "modify c1".getBytes(), -1);
//      zk.setData("/super/c2", "modify c2".getBytes(), -1);
//      byte[] data = zk.getData("/super/c2", false, null);
//      System.out.println(new String(data));       

//      //判斷節點是否存在
//      System.out.println(zk.exists("/super/c3", false));
//      //刪除節點
//      zk.delete("/super/c3", -1);

        zk.close();

    }
}

先啓動Client1,client1打印

開始連接ZK服務器
受影響的path : null
成功連接上ZK服務器

再啓動Client2,

開始連接ZK服務器
受影響的path : null
成功連接上ZK服務器
--------------------------------------------

再啓動Test類,此時Client1和Client2打印的內容是

開始連接ZK服務器
受影響的path : null
成功連接上ZK服務器
--------------------------------------------  
受影響的path : /super
子節點 ... 變更
這個是新增的子節點 : /super/c3
cowaList: [c3]
paths: [c3]

Test打印的內容是

zk 建立連接
/super/c3

更多測試,需要自己來運行並且思考結果。這裏只起拋磚引玉的作用

發佈了137 篇原創文章 · 獲贊 211 · 訪問量 59萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章