安裝
關於zookeeper的安裝,請參考這篇文章:ZooKeeper偽分布式叢集安裝及使用
我在這裡使用的是文中提到的zookeeper偽分布模式的安裝和使用。
命令列
下面說明命令列方式下使用zookeeper。
切換到zookeeper安裝目錄的bin目錄下:
命令:zkServer.sh start zoo0.cfg
該命令啟動zkServer伺服器。我這裡使用的是偽分布模式,三個伺服器都是本機,只不過採用不同的連接埠號碼而已,並配置在不同的設定檔中,即zoo0.cfg、zoo1.cfg、zoo2.cfg這三個檔案中配置了相應的參數和連接埠號碼。
啟動了三個伺服器之後,查看三個伺服器的狀態:
命令:zkServer.sh status zoo0.cfg
三個伺服器的狀態有兩種,一種跟隨狀態,一種處於領導狀態。
伺服器啟動之後,可以通過用戶端進行串連,如下:
命令:zkCli.sh -server centos:2181
其中centos是我的主機名稱,2181是設定檔中配置的用戶端串連伺服器的連接埠號碼,可以在zoo0.cfg檔案中查看到這個配置。相應的,按照我的偽分布配置,zkCli.sh -server centos:2182 這個命令可以串連到我的第二個伺服器,該配置可以在zoo1.cfg檔案中。大家在使用該命令時,需要按照自己的配置相應的更改一下即可。
通過上面的日誌,Welcom to ZooKeeper。輸出說明用戶端串連伺服器成功。。
輸入help命令:
通過該命令,我們可以查看到用戶端可以使用的命令列有哪些。。。
其實也不多,就這麼幾個。
命令:
ls /
stat /zookeeper
ls /命令查看根節點下的節點有哪些。可以看到只有zookeeper一個節點。
stat /zookeeper命令查看節點zookeeper節點的狀態資訊。
cZxid表示建立該節點時候的zxid,
mZxid表示當前的,
zxid是用來為選舉leader服務的。
ctime和mtime,前者表示建立的時間,後者表示最近一次更新的時間。
要注意的是ctime就定死了建立的那一刻,而mtime會在你調用更改節點函數的時候重新設定;
但是exists和get是不會引起其更新的。
命令:get /nodename
命令get /nodename擷取節點路徑的資料。注意:path必須是絕對路徑,也就是path必須是/開頭的路徑。
剛開始節點沒有儲存資料,所以,這裡的三個節點下均無資料。
擷取子節點quota資訊:
設定資料命令:
set /nodepath 資料資訊
圖中設定根節點的資料為字串“longyin”,然後命令擷取:get /
可以看到dataLength的數值也跟著變化了。
建立節點:create /path data
建立節點node,然後通過ls輸出根節點下的子節點有兩個。
擷取資料並刪除節點,命令:
get /node
delete /node
退出用戶端,命令:
quit
java API 介面操作
import java.io.IOException;import org.apache.zookeeper.CreateMode;import org.apache.zookeeper.KeeperException;import org.apache.zookeeper.WatchedEvent;import org.apache.zookeeper.Watcher;import org.apache.zookeeper.ZooDefs.Ids;import org.apache.zookeeper.ZooKeeper;public class BasicDemo1 { public static void main(String[] args) throws IOException, KeeperException, InterruptedException { /** * 建立一個與伺服器的串連 * 參數一:伺服器位址和連接埠號碼(該連接埠號碼值伺服器允許用戶端串連的連接埠號碼,設定檔的預設連接埠號碼2181) * 參數二:串連會話逾時時間 * 參數三:觀察者,串連成功會觸發該觀察者。不過只會觸發一次。 * 該Watcher會擷取各種事件的通知 */ ZooKeeper zk = new ZooKeeper("centos:2181", 60000, new Watcher() { // 監控所有被觸發的事件 public void process(WatchedEvent event) { System.out.println("監控所有被觸發的事件:EVENT:" + event.getType()); } }); System.out.println("*******************************************************"); // 查看根節點的子節點 System.out.println("查看根節點的子節點:ls / => " + zk.getChildren("/", true)); System.out.println("*******************************************************"); // 建立一個目錄節點 if (zk.exists("/node", true) == null) { /** * 參數一:路徑地址 * 參數二:想要儲存的資料,需要轉換成位元組數組 * 參數三:ACL存取控制清單(Access control list), * 參數類型為ArrayList<ACL>,Ids介面提供了一些預設的值可以調用。 * OPEN_ACL_UNSAFE This is a completely open ACL * 這是一個完全開放的ACL,不安全 * CREATOR_ALL_ACL This ACL gives the * creators authentication id's all permissions. * 這個ACL賦予那些授權了的使用者具備許可權 * READ_ACL_UNSAFE This ACL gives the world the ability to read. * 這個ACL賦予使用者讀的許可權,也就是擷取資料之類的許可權。 * 參數四:建立的節點類型。枚舉值CreateMode * PERSISTENT (0, false, false) * PERSISTENT_SEQUENTIAL (2, false, true) * 這兩個類型建立的都是持久型類型節點,回話結束之後不會自動刪除。 * 區別在於,第二個類型所建立的節點名後會有一個單調遞增的數值 * EPHEMERAL (1, true, false) * EPHEMERAL_SEQUENTIAL (3, true, true) * 這兩個類型所建立的是臨時型類型節點,在回話結束之後,自動刪除。 * 區別在於,第二個類型所建立的臨時型節點名後面會有一個單調遞增的數值。 * 最後create()方法的傳回值是建立的節點的實際路徑 */ zk.create("/node", "conan".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); System.out.println("建立一個目錄節點:create /node conan"); /** * 查看/node節點資料,這裡應該輸出"conan" * 參數一:擷取節點的路徑 * 參數二:說明是否需要觀察該節點,設定為true,則設定共用預設的觀察器 * 參數三:stat類,儲存節點的資訊。例如資料版本資訊,建立時間,修改時間等資訊 */ System.out.println("查看/node節點資料:get /node => " + new String(zk.getData("/node", false, null))); /** * 查看根節點 * 在此查看根節點的值,這裡應該輸出上面所建立的/node節點 */ System.out.println("查看根節點:ls / => " + zk.getChildren("/", true)); } System.out.println("*******************************************************"); // 建立一個子目錄節點 if (zk.exists("/node/sub1", true) == null) { zk.create("/node/sub1", "sub1".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); System.out.println("建立一個子目錄節點:create /node/sub1 sub1"); // 查看node節點 System.out.println("查看node節點:ls /node => " + zk.getChildren("/node", true)); } System.out.println("*******************************************************"); /** * 修改節點資料 * 修改的資料會覆蓋上次所設定的資料 * setData()方法參數一、參數二不多說,與上面類似。 * 參數三:數值型。需要傳入該介面的數實值型別版本號碼。。。 * 該資訊可以通過Stat類擷取,也可以通過命令列擷取。 * 如果該值設定為-1,就是忽視版本匹配,直接設定節點儲存的值。 */ if (zk.exists("/node", true) != null) { zk.setData("/node", "changed".getBytes(), -1); // 查看/node節點資料 System.out.println("修改節點資料:get /node => " + new String(zk.getData("/node", false, null))); } System.out.println("*******************************************************"); // 刪除節點 if (zk.exists("/node/sub1", true) != null) { zk.delete("/node/sub1", -1); zk.delete("/node", -1); // 查看根節點 System.out.println("刪除節點:ls / => " + zk.getChildren("/", true)); } // 關閉串連 zk.close(); }}
代碼中大部分關鍵代碼已經給出了詳細的注釋。程式碼完成的功能也和命令列相同。首先啟動zookeeper,然後運行代碼:
結果如下:
可以看到,結果和我們使用命令列的操作一樣的效果。代碼中我們可以使用觀察者,接收各種事件。
最後再給出一個小範例程式碼:
import java.io.IOException;import java.util.ArrayList;import org.apache.zookeeper.CreateMode;import org.apache.zookeeper.KeeperException;import org.apache.zookeeper.WatchedEvent;import org.apache.zookeeper.Watcher;import org.apache.zookeeper.ZooKeeper;import org.apache.zookeeper.ZooDefs.Ids;public class QueueZooKeeper { public static void main(String[] args) throws Exception { if (args.length == 0) { doOne(); } else { doAction(Integer.parseInt(args[0])); } } public static void doOne() throws Exception { String host1 = "centos:2181"; ZooKeeper zk = connection(host1); initQueue(zk); joinQueue(zk, 1); joinQueue(zk, 2); joinQueue(zk, 3); zk.close(); } public static void doAction(int client) throws Exception { String host1 = "centos:2181"; String host2 = "centos:2182"; String host3 = "centos:2183"; ZooKeeper zk = null; switch (client) { case 1: zk = connection(host1); initQueue(zk); joinQueue(zk, 1); break; case 2: zk = connection(host2); initQueue(zk); joinQueue(zk, 2); break; case 3: zk = connection(host3); initQueue(zk); joinQueue(zk, 3); break; } } // 建立一個與伺服器的串連 public static ZooKeeper connection(String host) throws IOException { ZooKeeper zk = new ZooKeeper(host, 60000, new Watcher() { // 監控所有被觸發的事件 public void process(WatchedEvent event) { if (event.getType() == Event.EventType.NodeCreated && event.getPath().equals("/queue/start")) { System.out.println("Queue has Completed.Finish testing!!!"); } } }); return zk; } public static void initQueue(ZooKeeper zk) throws KeeperException, InterruptedException { System.out.println("WATCH => /queue/start");// zk.exists("/queue/start", true); /** * 建立節點/queue.永久節點 * 存在則不建立,如果不存在,則建立 */ if (zk.exists("/queue", false) == null) { System.out.println("create=> /queue task-queue"); zk.create("/queue", "task-queue".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } else { System.out.println("/queue is exist!"); } } /** * 建立節點/queue的子節點,(臨時節點),在會話退出之後,臨時節點刪除。並且臨時 * 節點有順序號。 * @param zk * @param x * @throws KeeperException * @throws InterruptedException */ public static void joinQueue(ZooKeeper zk, int x) throws KeeperException, InterruptedException { System.out.println("create=> /queue/x" + x + " x" + x); zk.create("/queue/x" + x, ("x" + x).getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); isCompleted(zk); } public static void isCompleted(ZooKeeper zk) throws KeeperException, InterruptedException { int size = 3; ArrayList<String> list = (ArrayList<String>) zk.getChildren("/queue", true); for (String str:list) { System.out.println("擷取節點queue的子節點:ls /queue:"+str); } int length = list.size(); System.out.println("Queue Complete:" + length + "/" + size+"(子節點個數/總長度)"); if (length >= size) { System.out.println("建立臨時型節點:create /queue/start start"); zk.create("/queue/start", "start".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); } }}
運行結果如下:
從上面的結果可以看出,我們建立臨時型節點的時候,會話結束,臨時節點自動刪除。我們使用命令列查看一下根節點的資訊:
代碼中建立的節點都沒有了。
另一個要注意的是,我們使用的節點類型是臨時型的有順序號的節點類型,我們發現,會話期間,我們列印的節點名後面有000000014,000000015,000000016,由於我運行了幾次代碼,所以這個值在單條遞增,增加到了16,從這裡我們應該知道了順序號類型的節點的意思了。
OK,結束。
本項目的代碼地址:請猛戳這裡(歡迎關注我的GITHUB)
項目使用eclipse構建。方便易用,代碼注釋詳細。