Zookeeper官網文檔—第三章 2.Zookeeper Java例子

ZooKeeper Java 例子

一個簡單的觀察者客戶端

爲了向你介紹Java API, 我們開發了一個非常簡單的觀察者客戶端. 這個Zookeeper客戶端觀察一個Zookeeper節點的變化,並且通過開始和結束應用程序來響應.

必要條件

客戶端有4個要求:

  • 它作爲參數:

    • Zookeeper服務的地址

    • znode的名稱 - 要觀察的那個節點

    • 具有參數的可執行文件.

  • 它獲取與znode關聯的數據,啓動可執行文件.

  • 如果znode改變, 客戶端重新獲取內容並重啓執行文件.

  • 如果znode文件消失,則客戶端殺死執行文件.

程序定義

通常, ZooKeeper應用分爲兩個單元,一個維持連接,一個監控數據. 在應用程序中,名爲Executor的類維持Zookeeper連接,名爲DataMonitor 的類監控Zookeeper樹的數據變化. 此外, Executor也包含主線程和包含執行邏輯. 它負責與用戶進行交互,同時也負責通過一個參數與可執行程序性交互,根據訂單狀態去關閉和重啓應用.

The Executor Class

Executor對象是樣例容器的主要容器. 包含ZooKeeper 對象, DataMonitor, 如下所述的程序設計Program Design.

    //來自Executor類...
    
    public static void main(String[] args) {
        if (args.length < 4) {
            System.err
                    .println("USAGE: Executor hostPort znode filename program [args ...]");
            System.exit(2);
        }
        String hostPort = args[0];
        String znode = args[1];
        String filename = args[2];
        String exec[] = new String[args.length - 3];
        System.arraycopy(args, 3, exec, 0, exec.length);
        try {
            new Executor(hostPort, znode, filename, exec).run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Executor(String hostPort, String znode, String filename,
            String exec[]) throws KeeperException, IOException {
        this.filename = filename;
        this.exec = exec;
        zk = new ZooKeeper(hostPort, 3000, this);
        dm = new DataMonitor(zk, znode, null, this);
    }

    public void run() {
        try {
            synchronized (this) {
                while (!dm.dead) {
                    wait();
                }
            }
        } catch (InterruptedException e) {
        }
    }

回想一下,Executor的工作是啓動和停止可執行文件,也就是命令行傳入的那個名稱. 它這樣做是爲了響應Zookeeper對象觸發的事件. 正如你上面看到代碼, 執行器在Zookeeper類構造函數中傳入一個自身作爲Watcher類型參數. 它還將自身作爲DataMonitorListener參數傳入DataMonitor構造函數. 每一個Executor的定義,都實現了這些接口:

public class Executor implements Watcher, Runnable, DataMonitor.DataMonitorListener {
...

Watcher 接口是在Zookeeper Java API中定義. ZooKeeper使用它和持有它的容器通訊. 它僅有一個方法,process(), Zookeeper使用它來交互一些主線程感興趣的事件, 例如Zookeeper的連接狀態或者ZooKeeper會話. 在這個例子中,Executor只是簡單的轉發一些事件去DataMonitor中,以確定它們去做什麼. 它只爲了說明一點,按照慣例,Executor或者看起來像是Executor的類擁有Zookeeper連接對象,但是它會委派事件給其他的類. 它還將此作爲監視事件的默認通道 (稍後詳細敘述)

    public void process(WatchedEvent event) {
        dm.process(event);
    }

 DataMonitorListener 接口, 另一方面,它不屬於Zookeeper API部分. 它是自定義接口, 爲了這個樣例程序而設計. DataMonitor使用它和它的擁有容器通訊,也就是Executor對象. DataMonitorListener看起來像這樣:

public interface DataMonitorListener {
    /**
    * 節點的存在狀態已經改變.
    */
    void exists(byte data[]);

    /**
    * ZooKeeper會話不在有效.
    * 
    * @param rc
    * ZooKeeper原因碼
    */
    void closing(int rc);
}

這個接口在DataMonitor類中定義和在Executor中實現. 當Executor.exists()被執行,Executor會根據需求啓動和停止. 回想一下必要條件,當znode節點不存在時,需要殺死可執行文件.

Executor.closing()被調用, Executor會決定自己是否關閉自身,來響應Zookeeper連接的永久消失.

你可能已經猜到了, DataMonitor是一個調用這些方法的對象,來響應Zookeeper狀態的改變.

這兒有Executor的DataMonitorListener.exists()DataMonitorListener.closing實現:

public void exists( byte[] data ) {
    if (data == null) {
        if (child != null) {
            System.out.println("Killing process");
            child.destroy();
            try {
                child.waitFor();
            } catch (InterruptedException e) {
            }
        }
        child = null;
    } else {
        if (child != null) {
            System.out.println("Stopping child");
            child.destroy();
            try {
               child.waitFor();
            } catch (InterruptedException e) {
            e.printStackTrace();
            }
        }
        try {
            FileOutputStream fos = new FileOutputStream(filename);
            fos.write(data);
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            System.out.println("Starting child");
            child = Runtime.getRuntime().exec(exec);
            new StreamWriter(child.getInputStream(), System.out);
            new StreamWriter(child.getErrorStream(), System.err);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public void closing(int rc) {
    synchronized (this) {
        notifyAll();
    }
}

The DataMonitor Class

DataMonitor類是Zookeeper邏輯的類. 它主要是異步和事件驅動的. DataMonitor將在構造函數中做的事情:

public DataMonitor(ZooKeeper zk, String znode, Watcher chainedWatcher,
        DataMonitorListener listener) {
    this.zk = zk;
    this.znode = znode;
    this.chainedWatcher = chainedWatcher;
    this.listener = listener;
    
    //檢查這個節點是否存在開始工作,我們將要完成事件驅動
    zk.exists(znode, true, this, null);
}

調用ZooKeeper.exists()來檢查znode是存在的,設置一個監聽者,並將本身作爲回調對象. 某種意義上來講,它只是將事情啓動,因爲當監聽者被觸發時才真正處理開始.

注意

不要將完成回調和監聽回調混淆. ZooKeeper.exists()是完成回調,它發生在DataMonitor對象的StatCallback.processResult()方法中實現, 當服務中監聽者操作完成異步設置,它會被調用.

監聽者觸發,另一方面,發送一個事件去Executor對象,因爲Executor註冊了一個Zookeeper對象的監聽者.

另外,你可能也注意到DataMonitor也將自身註冊爲了一個更詳細的事件監聽者. 這是新的Zookeeper3.0.0提供的特性(支持多觀察者). 但是,在這個例子中,DataMonitor並沒有註冊爲一個觀察者.

ZooKeeper.exists()操作在服務器上完成時,在客戶端ZooKeeper API會調用這個方法完成回調.

public void processResult(int rc, String path, Object ctx, Stat stat) {
    boolean exists;
    switch (rc) {
    case Code.Ok:
        exists = true;
        break;
    case Code.NoNode:
        exists = false;
        break;
    case Code.SessionExpired:
    case Code.NoAuth:
        dead = true;
        listener.closing(rc);
        return;
    default:
        // Retry errors
        zk.exists(znode, true, this, null);
        return;
    }
 
    byte b[] = null;
    if (exists) {
        try {
            b = zk.getData(znode, false, null);
        } catch (KeeperException e) {
            // We don't need to worry about recovering now. The watch callbacks will kick off any exception handling
            e.printStackTrace();
        } catch (InterruptedException e) {
            return;
        }
    }     
    if ((b == null && b != prevData)
            || (b != null && !Arrays.equals(prevData, b))) {
        listener.exists(b);
        prevData = b;
    }
}

代碼首先檢查了關於節點存在的錯誤代碼、致命錯誤、可恢復錯誤. 如果文件(或者znode)存在,如果znode存在它會獲取數據,如果狀態改變會接着調用Executor的exitsts回調. 注意,不需要對getData做任何異常處理,因爲它會監聽等待任何可能導致錯誤的異常: 如果在調用Zookeeper.getData()方法前節點被刪除,由Zookeeper.exists()設置的監聽事件觸發一個回調;如果有一個通訊錯誤,當連接恢復時,連接事件將會觸發.

最後,注意DataMonitor怎樣處理監聽事件:

    public void process(WatchedEvent event) {
        String path = event.getPath();
        if (event.getType() == Event.EventType.None) {
            // 我們被告知,連接狀態已經改變
            switch (event.getState()) {
            case SyncConnected:
                // 在這個特殊的例子中,我們不需要做任何事情- 監聽者會自動重新註冊的服務器和任何監聽者觸發客戶端斷開時都會交付。
                break;
            case Expired:
                // 終止一切
                dead = true;
                listener.closing(KeeperException.Code.SessionExpired);
                break;
            }
        } else {
            if (path != null && path.equals(znode)) {
                //在這個節點中有些事情發生了改變,讓我們找到答案
                zk.exists(znode, true, this, null);
            }
        }
        if (chainedWatcher != null) {
            chainedWatcher.process(event);
        }
    }

如果在會話失效以前客戶端zookeeper庫能夠和ZooKeeper建立連接通道。則所有的會話監控器會與服務自動重新建立(自動重置監控器是zookeeper3.0.0裏的一個新特性). 想了解更多請查看ZooKeeper Watche.  在這方法中,當DataMonitor獲得一個znode的事件時,它會調用calls ZooKeeper.exists()去發現改變了什麼.

Complete Source Listings

Executor.java
/**
 * 一個簡單的例子,基於znode使用DataMonitor去開始和停止一個可執行文件.
* 這個程序監聽指定的znode,保存到相應的文件系統znode節點.
* 當節點存在會啓動帶參數的指定程序,當znode接點被刪除時會關閉該程序. */ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; public class Executor implements Watcher, Runnable, DataMonitor.DataMonitorListener { String znode; DataMonitor dm; ZooKeeper zk; String filename; String exec[]; Process child; public Executor(String hostPort, String znode, String filename, String exec[]) throws KeeperException, IOException { this.filename = filename; this.exec = exec; zk = new ZooKeeper(hostPort, 3000, this); dm = new DataMonitor(zk, znode, null, this); } /** * @param args */ public static void main(String[] args) { if (args.length < 4) { System.err .println("USAGE: Executor hostPort znode filename program [args ...]"); System.exit(2); } String hostPort = args[0]; String znode = args[1]; String filename = args[2]; String exec[] = new String[args.length - 3]; System.arraycopy(args, 3, exec, 0, exec.length); try { new Executor(hostPort, znode, filename, exec).run(); } catch (Exception e) { e.printStackTrace(); } } /*************************************************************************** * 我們不需要處理任何事件,我們只需要將請求轉發給dm. * * @see org.apache.zookeeper.Watcher#process(org.apache.zookeeper.proto.WatcherEvent) */ public void process(WatchedEvent event) { dm.process(event); } public void run() { try { synchronized (this) { while (!dm.dead) { wait(); } } } catch (InterruptedException e) { } } public void closing(int rc) { synchronized (this) { notifyAll(); } } static class StreamWriter extends Thread { OutputStream os; InputStream is; StreamWriter(InputStream is, OutputStream os) { this.is = is; this.os = os; start(); } public void run() { byte b[] = new byte[80]; int rc; try { while ((rc = is.read(b)) > 0) { os.write(b, 0, rc); } } catch (IOException e) { } } } public void exists(byte[] data) { if (data == null) { if (child != null) { System.out.println("Killing process"); child.destroy(); try { child.waitFor(); } catch (InterruptedException e) { } } child = null; } else { if (child != null) { System.out.println("Stopping child"); child.destroy(); try { child.waitFor(); } catch (InterruptedException e) { e.printStackTrace(); } } try { FileOutputStream fos = new FileOutputStream(filename); fos.write(data); fos.close(); } catch (IOException e) { e.printStackTrace(); } try { System.out.println("Starting child"); child = Runtime.getRuntime().exec(exec); new StreamWriter(child.getInputStream(), System.out); new StreamWriter(child.getErrorStream(), System.err); } catch (IOException e) { e.printStackTrace(); } } } }
DataMonitor.java
/**
 * 一個簡單的類監控數據和Zookeeper節點. 它使用異步Zookeeper API.
 */
import java.util.Arrays;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.AsyncCallback.StatCallback;
import org.apache.zookeeper.KeeperException.Code;
import org.apache.zookeeper.data.Stat;

public class DataMonitor implements Watcher, StatCallback {

    ZooKeeper zk;

    String znode;

    Watcher chainedWatcher;

    boolean dead;

    DataMonitorListener listener;

    byte prevData[];

    public DataMonitor(ZooKeeper zk, String znode, Watcher chainedWatcher,
            DataMonitorListener listener) {
        this.zk = zk;
        this.znode = znode;
        this.chainedWatcher = chainedWatcher;
        this.listener = listener;
        // 如果這個節點存在檢查並開始程序. 我們完全由事件驅動.
        zk.exists(znode, true, this, null);
    }

    /**
     * 其他類通過實現這些方法來使用DataMonitor
     */
    public interface DataMonitorListener {
        /**
         * 節點的存在狀態已經改變.
         */
        void exists(byte data[]);

        /**
         * ZooKeeper會話不會再生效.
         *
         * @param rc
         *                Zookeeper原因代碼
         */
        void closing(int rc);
    }

    public void process(WatchedEvent event) {
        String path = event.getPath();
        if (event.getType() == Event.EventType.None) {
            // We are are being told that the state of the connection has changed
            switch (event.getState()) {
            case SyncConnected:
                // 在這個例子中,我們不需要做任何處理.
// 監聽者會自動重新註冊和任何觸發的客戶端都會重連. break; case Expired: // It's all over dead = true; listener.closing(KeeperException.Code.SessionExpired); break; } } else { if (path != null && path.equals(znode)) { // 在這個節點發生了改變,讓我們來看看 zk.exists(znode, true, this, null); } } if (chainedWatcher != null) { chainedWatcher.process(event); } } public void processResult(int rc, String path, Object ctx, Stat stat) { boolean exists; switch (rc) { case Code.Ok: exists = true; break; case Code.NoNode: exists = false; break; case Code.SessionExpired: case Code.NoAuth: dead = true; listener.closing(rc); return; default: // Retry errors zk.exists(znode, true, this, null); return; } byte b[] = null; if (exists) { try { b = zk.getData(znode, false, null); } catch (KeeperException e) { // 現在我們不需要擔心關於恢復了.
// 監聽者回調將會對任何異常進行處理. e.printStackTrace(); } catch (InterruptedException e) { return; } } if ((b == null && b != prevData) || (b != null && !Arrays.equals(prevData, b))) { listener.exists(b); prevData = b; } } }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章