ZooKeeper編程(一)

轉自:http://www.cnblogs.com/zhangchaoyang/articles/3813217.html

雜記

ZooKeeper的用途:distributed coordination;maintaining configuration information, naming, providing distributed synchronization, and providing group services.

Zookeeper的節點都是存放在內存中的,所以讀寫速度很快。更新日誌被記錄到了磁盤中,以便用於恢復數據。在更新內在中節點數之前,會先序列化到磁盤中。

爲避免單點失效,zookeeper的數據是在多個server上留有備份的。不管客戶端連接到的是哪個server,它看到的數據都是一致的。如果client和一個server的TCP連接失效,它會嘗試連接另一個server。衆多server中有一個是leader。

所有的server 都必須知道彼此的存在。

zookeeper在讀寫比例爲10:1時性能最佳。

每個znode上data的讀寫都是原子操作。

讀是局部性的,即client只需要從與它相連的server上讀取數據即可;而client有寫請求的話,與之相連的server會通知leader,然後leader會把寫操作分發給所有server。所以定要比讀慢很多。

在建立zookeeper連接時,給定的地址字符串可以是這樣的:"192.168.1.1:3000,192.168.1.2:3000,192.168.1.3:3000/app/a",以後的所有操作就都是在/app/a下進行的。實際上只連接到一臺ZooKeeper機器就可了,沒必要指定每臺zk機器的IP和端口,即用“192.168.1.2:3000/app/a”也是可以的。

當client與一個server斷連接時(可能是因爲server失效了),它就收不到任何watches;當它與另一個server建立好連接後,它就會收到"session expired"通知。

ACL不是遞歸的,它只針對當前節點,對子節點沒有任何影響。

默認情況下日誌文件和數據文件是放在同一個目錄下的,爲縮短延遲提高響應性,你可以把日誌文件單獨放在另一個目錄下。

爲避免swaping,運行java時最好把可用物理內在調得大一些,比如對於4G的內在,可以把它調到3G。java有以下兩個運行參數:

-Xms<size>
設置虛擬機可用內存堆的初始大小,缺省單位爲字節,該大小爲1024的整數倍並且要大於1MB,可用k(K)或m(M)爲單位來設置較大的內存數。初始堆大小爲2MB。
例如:-Xms6400K,-Xms256M
-Xmx<size>
設置虛擬機內存堆的最大可用大小,缺省單位爲字節。該值必須爲1024整數倍,並且要大於2MB。可用k(K)或m(M)爲單位來設置較大的內存數。缺省堆最大值爲64MB。
例如:-Xmx81920K,-Xmx80M

CreateMode

PERSISTENT:創建後只要不刪就永久存在

EPHEMERAL:會話結束年結點自動被刪除,EPHEMERAL結點不允許有子節點

SEQUENTIAL:節點名末尾會自動追加一個10位數的單調遞增的序號,同一個節點的所有子節點序號是單調遞增的

PERSISTENT_SEQUENTIAL:結合PERSISTENT和SEQUENTIAL

EPHEMERAL_SEQUENTIAL:結合EPHEMERAL和SEQUENTIAL

複製代碼
package basic;

import java.io.IOException;
import java.util.List;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;

public class Demo {
    private static final int TIMEOUT = 3000;

    public static void main(String[] args) throws IOException {
        ZooKeeper zkp = new ZooKeeper("localhost:2181", TIMEOUT, null);
        try {
            // 創建一個EPHEMERAL類型的節點,會話關閉後它會自動被刪除
            zkp.create("/node1", "data1".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);
            if (zkp.exists("/node1", false) != null) {
                System.out.println("node1 exists now.");
            }
            try {
                // 當節點名已存在時再去創建它會拋出KeeperException(即使本次的ACL、CreateMode和上次的不一樣)
                zkp.create("/node1", "data1".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
            } catch (KeeperException e) {
                System.out.println("KeeperException caught:" + e.getMessage());
            }

            // 關閉會話
            zkp.close();
            
            zkp = new ZooKeeper("localhost:2181", TIMEOUT, null);
            //重新建立會話後node1已經不存在了
            if (zkp.exists("/node1", false) == null) {
                System.out.println("node1 dosn't exists now.");
            }
            //創建SEQUENTIAL節點
            zkp.create("/node-", "same data".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT_SEQUENTIAL);
            zkp.create("/node-", "same data".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT_SEQUENTIAL);
            zkp.create("/node-", "same data".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT_SEQUENTIAL);
            List<String> children = zkp.getChildren("/", null);
            System.out.println("Children of root node:");
            for (String child : children) {
                System.out.println(child);
            }

            zkp.close();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
複製代碼

第一次運行輸出:

node1 exists now.
KeeperException caught:KeeperErrorCode = NodeExists for /node1
node1 dosn't exists now.
Children of root node:
node-0000000003
zookeeper
node-0000000002
node-0000000001

第二次運行輸出:

node1 exists now.
KeeperException caught:KeeperErrorCode = NodeExists for /node1
node1 dosn't exists now.
Children of root node:
node-0000000003
zookeeper
node-0000000002
node-0000000001
node-0000000007
node-0000000005
node-0000000006

注意兩次會話中創建的PERSISTENT_SEQUENTIAL節點序號並不是連續的,比如上例中缺少了node-0000000004.

Watcher & Version

watcher分爲兩大類:data watches和child watches。getData()和exists()上可以設置data watches,getChildren()上可以設置child watches。

setData()會觸發data watches;

create()會觸發data watches和child watches;

delete()會觸發data watches和child watches.

如果對一個不存在的節點調用了exists(),並設置了watcher,而在連接斷開的情況下create/delete了該znode,則watcher會丟失。

在server端用一個map來存放watcher,所以相同的watcher在map中只會出現一次,只要watcher被回調一次,它就會被刪除----map解釋了watcher的一次性。比如如果在getData()和exists()上設置的是同一個data watcher,調用setData()會觸發data watcher,但是getData()和exists()只有一個會收到通知。

複製代碼
 1 import java.io.IOException;
 2 
 3 import org.apache.zookeeper.CreateMode;
 4 import org.apache.zookeeper.KeeperException;
 5 import org.apache.zookeeper.WatchedEvent;
 6 import org.apache.zookeeper.Watcher;
 7 import org.apache.zookeeper.ZooDefs.Ids;
 8 import org.apache.zookeeper.ZooKeeper;
 9 import org.apache.zookeeper.data.Stat;
10 
11 public class SelfWatcher implements Watcher{
12     
13     ZooKeeper zk=null;
14 
15     @Override
16     public void process(WatchedEvent event) {
17         System.out.println(event.toString());
18     }
19     
20     SelfWatcher(String address){
21         try{
22             zk=new ZooKeeper(address,3000,this);     //在創建ZooKeeper時第三個參數負責設置該類的默認構造函數
23             zk.create("/root", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
24         }catch(IOException e){
25             e.printStackTrace();
26             zk=null;
27         }catch (KeeperException e) {
28             e.printStackTrace();
29         } catch (InterruptedException e) {
30             e.printStackTrace();
31         }
32     }
33     
34     void setWatcher(){
35         try {
36             Stat s=zk.exists("/root", true);
37             if(s!=null){
38                 zk.getData("/root", false, s);
39             }
40         } catch (KeeperException e) {
41             e.printStackTrace();
42         } catch (InterruptedException e) {
43             e.printStackTrace();
44         }
45     }
46     
47     void trigeWatcher(){
48         try {
49             Stat s=zk.exists("/root", false);        //此處不設置watcher
50             zk.setData("/root", "a".getBytes(), s.getVersion());    //修改數據時需要提供version,version設爲-1表示強制修改
51         }catch(Exception e){
52             e.printStackTrace();
53         }
54     }
55     
56     void disconnect(){
57         if(zk!=null)
58             try {
59                 zk.close();
60             } catch (InterruptedException e) {
61                 e.printStackTrace();
62             }
63     }
64     
65     public static void main(String[] args){
66         SelfWatcher inst=new SelfWatcher("127.0.0.1:2181");
67         inst.setWatcher();
68         inst.trigeWatcher();
69         inst.disconnect();
70     }
71 
72 }
複製代碼

可以在創建Zookeeper時指定默認的watcher回調函數,這樣在getData()、exists()和getChildren()收到通知時都會調用這個函數--只要它們在參數中設置了true。所以如果把代碼22行的this改爲null,則不會有任何watcher被註冊。

上面的代碼輸出:

WatchedEvent state:SyncConnected type:None path:null
WatchedEvent state:SyncConnected type:NodeDataChanged path:/root

之所會輸出第1 行是因爲本身在建立ZooKeeper連接時就會觸發watcher。輸出每二行是因爲在代碼的第36行設置了true。

WatchEvent有三種類型:NodeDataChanged、NodeDeleted和NodeChildrenChanged。

調用setData()時會觸發NodeDataChanged;

調用create()時會觸發NodeDataChanged和NodeChildrenChanged;

調用delete()時上述三種event都會觸發。

如果把代碼的第36--39行改爲:

Stat s=zk.exists("/root", false);
if(s!=null){
    zk.getData("/root", true, s);
}

Stat s=zk.exists("/root", true);
if(s!=null){
    zk.getData("/root", true, s);
}

跟上面的輸出是一樣的。這也證明了watcher是一次性的。

設置watcher的另外一種方式是不使用默認的watcher,而是在getData()、exists()和getChildren()中指定各自的watcher。示例代碼如下:

複製代碼
 1 public class SelfWatcher{
 2     
 3     ZooKeeper zk=null;
 4 
 5     private Watcher getWatcher(final String msg){
 6         return new Watcher(){
 7             @Override
 8             public void process(WatchedEvent event) {
 9                 System.out.println(msg+"\t"+event.toString());
10             }
11         };
12     }
13     
14     SelfWatcher(String address){
15         try{
16             zk=new ZooKeeper(address,3000,null);     //在創建ZooKeeper時第三個參數負責設置該類的默認構造函數
17             zk.create("/root", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
18         }catch(IOException e){
19             e.printStackTrace();
20             zk=null;
21         }catch (KeeperException e) {
22             e.printStackTrace();
23         } catch (InterruptedException e) {
24             e.printStackTrace();
25         }
26     }
27     
28     void setWatcher(){
29         try {
30             Stat s=zk.exists("/root", getWatcher("EXISTS"));
31             if(s!=null){
32                 zk.getData("/root", getWatcher("GETDATA"), s);
33             }
34         } catch (KeeperException e) {
35             e.printStackTrace();
36         } catch (InterruptedException e) {
37             e.printStackTrace();
38         }
39     }
40     
41     void trigeWatcher(){
42         try {
43             Stat s=zk.exists("/root", false);        //此處不設置watcher
44             zk.setData("/root", "a".getBytes(), s.getVersion());
45         }catch(Exception e){
46             e.printStackTrace();
47         }
48     }
49     
50     void disconnect(){
51         if(zk!=null)
52             try {
53                 zk.close();
54             } catch (InterruptedException e) {
55                 e.printStackTrace();
56             }
57     }
58     
59     public static void main(String[] args){
60         SelfWatcher inst=new SelfWatcher("127.0.0.1:2181");
61         inst.setWatcher();
62         inst.trigeWatcher();
63         inst.disconnect();
64     }
65 
66 }
複製代碼

輸出:

GETDATA WatchedEvent state:SyncConnected type:NodeDataChanged path:/root
EXISTS WatchedEvent state:SyncConnected type:NodeDataChanged path:/root

上例中由於exists和getData分別設置了兩個不同的Watcher實例,所以雖然watcher都是由同了一個NodeDataChanged觸發的,但exists()和getData()都會收到通知。由於16行創建Zookeeper時沒有設置watcher(參數爲null),所以建立連接時沒有收到通知。

關於Version:爲了方便進行cache validations 和coordinated updates,每個znode都有一個stat結構體,其中包含:version的更改記錄、ACL的更改記錄、時間戳。znode的數據每更改一次,version就會加1。客戶端每次檢索data的時候都會把data的version一併讀出出來。修改數據時需要提供version。

zk.delete("/root", -1);        //觸發data watches和children watches。version設爲-1時表示要強制刪除
zk.getChildren("/root", getWatcher("LISTCHILDREN"));    //getChildren()上可以設置children watches

輸出:

LISTCHILDREN WatchedEvent state:SyncConnected type:NodeDeleted path:/root

zk.delete("/root", -1);        //觸發data watches和children watches
Stat s=zk.exists("/root", getWatcher("EXISTS"));    //exists()上可以設置data watches
if(s!=null){
    zk.getChildren("/root", getWatcher("LISTCHILDREN"));
}

輸出:

EXISTS WatchedEvent state:SyncConnected type:NodeDeleted path:/root
LISTCHILDREN WatchedEvent state:SyncConnected type:NodeDeleted path:/root

zk.delete("/root", -1);        //觸發data watches和children watches
Stat s=zk.exists("/root", getWatcher("EXISTS"));
if(s!=null){
    zk.getData("/root", getWatcher("GETDATA"), s);
    zk.getChildren("/root", getWatcher("LISTCHILDREN"));
}

輸出:

GETDATA WatchedEvent state:SyncConnected type:NodeDeleted path:/root
LISTCHILDREN WatchedEvent state:SyncConnected type:NodeDeleted path:/root
EXISTS WatchedEvent state:SyncConnected type:NodeDeleted path:/root

複製代碼
tat s=zk.exists("/root", false);        
zk.setData("/root", "a".getBytes(), s.getVersion());        
zk.delete("/root", -1);        

Stat s=zk.exists("/root", getWatcher("EXISTS"));
if(s!=null){
    zk.getData("/root", getWatcher("GETDATA"), s);
    zk.getChildren("/root", getWatcher("LISTCHILDREN"));
}
複製代碼

輸出:

GETDATA WatchedEvent state:SyncConnected type:NodeDataChanged path:/root
EXISTS WatchedEvent state:SyncConnected type:NodeDataChanged path:/root
LISTCHILDREN WatchedEvent state:SyncConnected type:NodeDeleted path:/root

按說data watches觸發了兩次,但是exists()和getData()只會收到一次通知。

Barriers and Queues

Barrier是指:

1)所有的線程都到達barrier後才能進行後續的計算

或者

2)所有的線程都完成自己的計算後才能離開barrier

Double Barrier是指同時具有上述兩點。

Queue就不說了,一個產生--消費模型,先生產的先被消費。

Double Barrier的實現:

複製代碼
enter barrier:
1.建一個根節點"/root"
2.想進入barrier的線程在"/root"下建立一個子節點"/root/c_i"
3.循環監聽"/root"孩子節點數目的變化,當其達到size時就說明有size個線程都已經barrier點了

leave barrier:
1.想離開barrier的線程刪除其在"/root"下建立的子節點
2.循環監聽"/root"孩子節點數目的變化,當size減到0時它就可以離開barrier了
複製代碼

Queue的實現:

1.建立一個根節點"/root"
2.生產線程在"/root"下建立一個SEQUENTIAL子節點
3.消費線程檢查"/root"有沒有子節點,如果沒有就循環監聽"/root"子節點的變化,直到它有子節點。刪除序號最小的子節點。

原代碼:

Locks

複製代碼
獲得鎖:
1.創建根節點"/root"
2.在根節點下新建子節點"/root/c-xxxxxx",SEQUENTIAL模式
3.對根節點調用getChildren(),如果第2步創建的節點是所有子節點中序號最小的,則獲得鎖;否則進入第4步
4.在序號最小的子節點上調用exists(),當序號最小的子節點被刪除後返回第3步

釋放鎖:
刪除自己創建的子節點即可
複製代碼

原代碼:

讀鎖(共享鎖)和寫鎖(排斥鎖)並存的情況跟單獨只有排斥鎖的情況有幾點不同:

1.當一個線程想施加讀鎖時就新建一個節點"/root/read-xxxxxx",施加寫鎖時就新建一個節點"/root/write-xxxxxx";

2.欲施加讀鎖的線程查看"/root"下有沒有“write"開頭的節點,如果沒有則直接獲得讀鎖;如果有,但是"write"節點的序號比自己剛纔創建的"read"節點的序號要大說明是先施加的讀鎖後施加的寫鎖,所以依然獲得讀鎖;else,在序號最小的"write"節點上調用exists,等待它被刪除。

原文來自:博客園(華夏35度)http://www.cnblogs.com/zhangchaoyang 作者:Orisun
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章