Zookeeper入門——Zookeeper的Java客戶端API(1)

搜了一些Zookeeper的相關書籍和博客,但好多基本都是講Zookeeper的架構、用途,尤其分佈式中的應用講的真的是天花亂墜,但看完還是不會寫代碼,搞得自己理論豐富的一批,實踐完全懵逼。對於Zookeeper的Java客戶端API使用,基本沒有涉及或者講清楚,要麼就是講的很模糊。果然還是得自己來,通過Zookeeper的API來學習一下Zookeeper的功能,然後依據這些功能去思考怎麼用?爲什麼用?逐漸摸索Zookeeper的應用。

1. 環境準備

1. 安裝jdk,這就不細說了。

2.安裝Zookeeper集羣,網上很多詳細教程,但還是簡單貼出來自己的配置文件。可以直接複製。只是其中一個Zookeeper節點的配置文件,其餘兩個僅僅改動一下端口、dataDir以及dataLogDir的路徑即可。

# 服務器與客戶端之間交互的基本時間單元(ms) 
tickTime=2000   
#zookeeper集羣中的包含多臺server, 其中一臺爲leader, 集羣中其餘的server爲follower. initLimit參數配置初始化連接時, follower和leader之間的最長心跳時間. 此時該參數設置爲10, #說明時間限制爲10倍tickTime.
initLimit=10  
# 該參數配置leader和follower之間發送消息, 請求和應答的最大時間長度. 此時該參數設置爲5, 說明時間限制爲5倍tickTime. 
syncLimit=5
# zookeeper中使用的基本時間單位, 毫秒值.
tickTime=2000
#存儲內存中數據庫快照的位置
dataDir=/usr/local/zookeeper-cluster/zookeeper-2181/data
#存儲日誌的目錄,如果沒有設置該參數, 將使用和#dataDir相同的設置.
dataLogDir=/usr/local/zookeeper-cluster/zookeeper-2181/logs 
# 用於監聽客戶端連接的端口,或者說客戶端請求連接的端口
clientPort=2181

maxClientCnxns=60
#這一段很重要,集羣配置的關鍵,server.後面跟的數字是標識某臺Zookeeper的關鍵,後續配置集羣會使用
server.1=127.0.0.1:2222:2225
server.2=127.0.0.1:3333:3335
server.3=127.0.0.1:4444:4445

#autopurge.snapRetainCount=3

#autopurge.purgeInterval=1

3. 引入jar包,這裏推薦使用Apache的java客戶端。

		<dependency>
			<groupId>org.apache.zookeeper</groupId>
			<artifactId>zookeeper</artifactId>
			<version>3.5.7</version>
		</dependency>

2.API使用

1. 首先,創建Zookeeper客戶端對象。很簡單,直接new一個就行,但主要是注意構造方法的參數。Zookeeper構造方法重載版本比較多,必須瞭解其中的每個參數代表的含義。

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly, HostProvider aHostProvider, ZKClientConfig clientConfig) throws IOException 

(1)String connectString :Zookeeper集羣的每個節點的IP地址和端口號,用逗號分隔。例如

String zkNodes = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";

(2)int sessionTimeout:會話超時時間,單位是毫秒,在sessionTimeout時間之內,客戶端會和服務端直接通過server發送PING請求來保持會話的有效性,俗稱“心跳檢測”,同時server重新激活client對應的會話。

Session是指當Client創建一個同Server的連接時產生的會話。連接Connected之後Session狀態就開啓,Zookeeper服務器和Client採用長連接方式(Client會不停地向Server發送心跳)保證session在不出現網絡問題、服務器宕機或Client宕機情況下可以一直存在。因此,在正常情況下,session會一直有效,並且ZK集羣上所有機器都會保存這個Session信息。

如果超出sessionTimeout的時間服務端仍未接收到客戶端的心跳檢測請求,那麼服務端就會將客戶端看做下線狀態,會將存儲的session給刪除。在ZK中,很多數據和狀態都是和會話綁定的,一旦會話失效,那麼ZK就開始清除和這個會話有關的信息,包括這個會話創建的臨時節點和註冊的所有Watcher。

但還有另一種情況就是,客戶端正常,但是當前會話所連接的Zookeeper集羣節點宕機或者其他原因心跳檢測失敗,也就是無法ping,ZK Client會馬上捕獲到這個異常,封裝爲一個ConnectionLoss的事件,然後啓動自動重連機制在地址列表中選擇新的地址進行重連。重連會有三種結果:

  • 在session timeout時間內重連成功,client會重新收到一個syncconnected的event,並將連接重新持久化爲connected狀態
  • 超過session timeout時間段後重連成功,client會收到一個expired的event,並將連接持久化爲closed狀態
  • 一直重連不上,client將不會收到任何event

(3)Watcher watcher:這是Zookeeper中非常重要的一個特徵,由於客戶端與Zookeeper之間的連接是採用長連接,所以,可以通過客戶端在Zookeeper上註冊一個事件監聽器,也就是Watcher對象,當Zookeeper中發生某一個事件時就會回調該Watcher對象中的方法。這個Watcher對象的作用非常重要,這個事件監聽器將會一直存在,直到Zookeeper客戶端關閉連接。

(4)long sessionId:每一個會話session的建立,Zookeeper都會爲自動該會話分配一個全局唯一的會話id,所以一般該id不會由我們指定,一般都不會指定傳遞該參數。

(5)boolean canBeReadOnly:是否提供只讀服務(不提供寫服務)。

(6)HostProvider aHostProvider:隨機提供host進行連接。沒啥用,默認的即可。

(7)ZKClientConfig clientConfig:連接參數配置,

(8)byte[] sessionPasswd:提供連接zookeeper的sessionId和密碼,通過這兩個確定唯一一臺客戶端,目的是可以提供重複會話。

實際上,真正必須的參數也就是connectString、sessionTimeout和watcher,其餘的默認即可。所以Zookeeper也提供了這三個參數的重載版本,也是最常用的構造方法。

注意:zookeeper客戶端和服務器端會話的建立是一個異步的過程,也就是說在程序中,我們程序方法在處理完客戶端初始化後,立即返回(程序往下執行代碼,這樣,大多數情況下我們並沒有真正構建好一個可用會話,在會話的聲明週期處於"CONNECTING"時纔算真正建立完畢) 

創建Zookeeper客戶端並建立連接的示例代碼如下:

    public static void createZookeeperClient() throws IOException, InterruptedException {
        /*
        由於Zookeeper客戶端對象的建立和連接是異步執行,所以很有可能會因爲Zookeeper對象尚未建立連接,
        主線程就繼續執行導致的執行報錯,這一點可以通過一些線程同步類輔助,保證Zookeeper連接完全建立成功後在進行後續操作
         */
        final CountDownLatch countDownLatch = new CountDownLatch(1);
        ZooKeeper zooKeeper = new ZooKeeper(zkNodes, 5000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                //已建立連接
                if (watchedEvent.getState().equals(Event.KeeperState.SyncConnected)) {
                    countDownLatch.countDown();
                }
            }
        });
        countDownLatch.await();
        //do something...
    }

2. Watcher對象的使用:Watcher可以實現對Zookeeper的監控,這個特性是來自於Zookeeper客戶端和服務端採用TCP長連接的方式進行通信。當Zookeeper發生某個事件時,會通過Watcher對象進行回調通知。Watcher是一個接口,所以必須要實現一個具體實現類,僅有一個接口方法process(),該方法的參數WatchedEvent就是Zookeeper服務端發生的事件,其中包含了該事件的所有信息。客戶端在向zk服務器註冊Watcher的同時,會將Watcher對象存儲在客戶端的WatchManager中。當zk服務端觸發Watcher事件後,會向客戶端發送通知,客戶端線程從WatcherManager中取出對應的Watcher對象。來執行回調邏輯。

public class WatcherTest1 implements Watcher {
    @Override
    public void process(WatchedEvent watchedEvent) {
        
    }
}

對於WatchedEvent,其源碼比較簡單,其中只包含了三個主要的域變量,分別是EventType eventType, KeeperState keeperState, String path。eventType表示事件類型,keeperState表示連接狀態,path就表示事件的發生在哪個數據節點路徑上發生的。

public class WatchedEvent {
    private final KeeperState keeperState;
    private final EventType eventType;
    private String path;

   ····
}

EventType和KeeperState都是枚舉類型

        public static enum EventType {
            None(-1),//無此節點
            NodeCreated(1),//節點已創建成功
            NodeDeleted(2),//節點已刪除成功
            NodeDataChanged(3),//節點數據改變
            NodeChildrenChanged(4),//子節點被創建、被刪除會發生事件觸發
            DataWatchRemoved(5),//數據監視已被移除
            ChildWatchRemoved(6);//子節點監視已被移除

            private final int intValue;
          ....
        }
        public static enum KeeperState {
            
            
            Unknown(-1),//從3.1.0版本開始被廢棄
            Disconnected(0),//客戶端和服務器處於斷開連接狀態
            NoSyncConnected(1),//從3.1.0版本開始被廢棄
            SyncConnected(3),//客戶端和服務器處於連接狀態
            AuthFailed(4),//權限驗證失敗狀態,通常同時也會收到AuthFailedException
            ConnectedReadOnly(5),//只讀連接
            SaslAuthenticated(6),//權限驗證通過
            Expired(-112),//此時客戶端會話失效,通常同時也會收到SessionExpiredException
            Closed(7);//連接資源關閉

            ....
        }

Watcher特性

 一次性

無論是服務端還是客戶端,一旦一個 Watcher 被觸發,ZooKeeper 都將其從相應的存儲中移除。因此,開發人員在 Watcher 的使用上要記住的一點是需要反覆註冊。例如,如果客戶端執行 getData("/znode1",true),後面對 /znode1 的更改或刪除,客戶端都會獲得 /znode1 的監控事件通知。如果 /znode1 再次更改,如果客戶端沒有執行新一次設置新監視點的讀取,是不會發送監視事件通知的。但是,有一個監聽器對象是例外,在創建Zookeeper對象時,調用的構造方法中必須傳遞的Watcher對象就是和Zookeeper對象具有相同的生命週期,直到Zookeeper客戶端關閉連接,該Watcher纔會關閉對服務端的監聽。

客戶端串行執行

客戶端Watcher回調的過程是一個串行同步的過程,這爲我們保證了順序,同時,需要開發人員注意的一點是,千萬不要因爲一個Watcher的處理邏輯影響了整個客戶端的Watcher回調。

輕量

WatchedEvent 是 ZooKeeper 整個 Watcher 通知機制的最小通知單元,這個數據結構中只包含三部分內容:通知狀態、事件類型和節點路徑。也就是說,Watcher 通知非常簡單,只會告訴客戶端發生了事件,而不會說明事件的具體內容。

另外,客戶端向服務端註冊 Watcher 的時候,並不會把客戶端真實的 Watcher 對象傳遞給服務端,僅僅只是在客戶端請求中使用 boolean 類型屬性進行了標記,同時服務端也僅僅只是保存了當前連接的 ServerCnxn 對象。如此輕量的Watcher機制設計,在網絡開銷和服務端內存開銷上都是非常廉價的。

3. 創建節點:Zookeeper的Java客戶端對於數據操作(增刪改查)都有同步和異步兩種實現方式。同步操作一般會有返回值,並且會拋出相應的異常。異步操作沒有返回值,也不會拋出異常。此外異步方法參數在同步方法參數的基礎上,會增加Callback和context兩個參數。

同步創建節點:全參數版本

    public String create(String path, byte[] data, List<ACL> acl, CreateMode createMode, Stat stat, long ttl)

(1)path指的就是數據節點的路徑。

(2)data就是要存儲的字符串的字節數組形式。(不支持序列化方式,如果需要實現序列化,可使用java相關的序列化框架,如Hession)

(3)acl指節點權限,統一使用Ids.OPEN_ACL_UNSAFE權限即可(一般在權限沒有太高要求的場景下,沒必要關注)

(4)createMode節點類型,創建節點的類型:CreateMode.* 提供四種節點類型,分別是PERSISTENT (持久節點)、PERSISTENT_SEQUENTIAL(持久順序節點)、EPHEMRAL(臨時節點)、EPHEMRAL_SEQUENTIAL(臨時順序節點)

(5)stat節點狀態信息:在創建節點時可以手動指定節點的狀態信息,但一般無需傳入該參數。(非必須參數)

(6)ttl:過期時間,如果該節點在ttl時間之內未發生變動,就會被刪除。(非必須參數)

所以常用的同步創建節點方法的重載版本如下

public String create(String path, byte[] data, List<ACL> acl, CreateMode createMode)

示例代碼如下

public static void createNode() throws IOException, KeeperException, InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(1);
        ZooKeeper zooKeeper = new ZooKeeper(zkNodes, 5000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                //已建立連接
                if (watchedEvent.getState().equals(Event.KeeperState.SyncConnected)) {
                    countDownLatch.countDown();
                }
            }
        });
        countDownLatch.await();
        //節點權限設置
        ACL acl = new ACL(ZooDefs.Perms.ALL,ZooDefs.Ids.ANYONE_ID_UNSAFE);
        List<ACL> acls = new ArrayList<ACL>();
        acls.add(acl);
        //創建節點
        zooKeeper.create("/demo", "helloworld".getBytes(), acls, CreateMode.PERSISTENT);

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

異步創建節點:


public void create(String path, byte[] data, List<ACL> acl, CreateMode createMode, StringCallback cb, Object ctx) 

除了create同步方法中的四個必須參數以外,異步模式的create方法還增加了callback和context兩個參數。這個callback中的processResult方法會在節點創建好之後被調用,它有四個參數。第一個是int類型的resultCode,爲服務端響應碼, 0表示調用成功,-4表示端口連接,-110表示指定節點存在,-112表示會話已經過期。第二個參數是創建節點的路徑。第三個參數是context,當一個StringCallback類型對象作爲多個create方法的參數時,這個參數就很有用。第四個參數是創建節點的名字,其實與path參數相同。異步的方法不會拋出異常,而是會在回調StringCallback中處理所有的事件。

    public interface StringCallback extends AsyncCallback {
        void processResult(int var1, String var2, Object var3, String var4);
    }

示例代碼

    public static void createNode() throws IOException, KeeperException, InterruptedException {

        //創建連接對象省略


        final CountDownLatch countDownLatch = new CountDownLatch(1);
        //節點權限設置
        ACL acl = new ACL(ZooDefs.Perms.ALL,ZooDefs.Ids.ANYONE_ID_UNSAFE);
        List<ACL> acls = new ArrayList<ACL>();
        acls.add(acl);

        zk.create("/demo", "helloworld".getBytes(), acls, CreateMode.PERSISTENT,new IStringCallBack(), countDownLatch);
        countDownLatch.await();
        System.out.println("over");
    }

    static class IStringCallBack implements AsyncCallback.StringCallback {

        @Override
        public void processResult(int rc, String path, Object ctx, String name) {
            switch (KeeperException.Code.get(rc)) {
                case CONNECTIONLOSS:
                    System.out.println("CONNECTIONLOSS");
                    break;
                case OK:
                    System.out.println("OK - {" + path + ", " + name + ", " + ctx + "}");
                    CountDownLatch countDownLatch = (CountDownLatch) ctx;
                    countDownLatch.countDown();
                    break;
                case NODEEXISTS:
                    System.out.println(path + "exists");
                    break;
                default:
                    System.out.println("DEFAULT");
                    break;
            }
        }

    }

4. 刪除節點:和創建節點類似,分同步和異步。

(1)同步

public void delete(String path, int version)

參數path,表示節點路徑

參數version,表示版本號,即表示本次刪除操作是針對該數據的某個版本進行操作。只有當version參數的值與節點狀態信息中的dataVersion值相等時,數據修改才能成功,否則會拋出BadVersion異常。這是爲了防止丟失數據的更新,在ZooKeeper提供的API中,所有的對已有節點的寫數據操作都有version參數。

(2)異步

public void delete(String path, int version, VoidCallback cb, Object ctx)

5. 修改節點數據

(1)同步

public Stat setData(String path, byte[] data, int version)

(2)異步

public void setData(String path, byte[] data, int version, StatCallback cb, Object ctx)

參數path:表示數據節點路徑

參數data:表示要設置的數據

參數version:表示修改數據版本

6. 獲取節點的數據

(1)同步:

public byte[] getData(String path, boolean watch, Stat stat)

zooKeeper.getData方法的返回值就是節點中存儲的數據值,它有三個參數,第一個參數是節點的路徑,用於表示要獲取哪個節點中的數據。第三個參數stat用於存儲節點的狀態信息,在調用getData方法前,會先構造一個空的Stat類型對象作爲參數傳給getData方法,當getData方法調用返回後,節點的狀態信息會被填充到stat對象中。

private void getDataSync() throws KeeperException, InterruptedException {
    Stat stat = new Stat();
    // getData的返回值是該節點的數據值,節點的狀態信息會賦值給stat對象
    byte[] data = zooKeeper.getData("/node_1",true, stat);
    System.out.println(new String(data));
    System.out.println(stat);
}

第二個參數是一個bool類型的watch,這個參數比較重要。當watch爲true時,表示我們想要監控這個節點的數據變化,而使用的監聽器對象就是我們在創建Zookeeper客戶端時指定的那個監聽器對象,這個boolean參數時,false表示不對該節點監控。當節點的數據發生變化時,我們就可以拿到zk服務器推送給我們的通知。

第二個參數,我們還可以使用自定義的Watcher對象,但這種監控只能生效一次,這是getData方法的另一個重載版本

public byte[] getData(String path, Watcher watcher, Stat stat)

(2)異步

public void getData(String path, Watcher watcher, DataCallback cb, Object ctx)
public void getData(String path, boolean watch, DataCallback cb, Object ctx)
private void getDataAsync() {
        zooKeeper.getData("/node", true, new AsyncCallback.DataCallback() {
            public void processResult(int resultCode, String path, Object ctx, byte[] data, Stat stat) {
                System.out.println(resultCode);
                System.out.println(path);
                System.out.println(ctx);
                System.out.println(new String(data));//data就是獲取到的數據
                System.out.println(stat);
            }
        }, "異步獲取節點的數據");
    }

 

 

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