【分佈式技術專題】「Zookeeper中間件」給大家學習一下Zookeeper的”開發伴侶”—Curator-Framework(基礎篇)

CuratorFramework基本介紹

CuratorFramework是Netflix公司開源的一套Zookeeper客戶端框架,它作爲一款優秀的ZooKeeper客戶端開源工具,主要提供了對客戶端到服務的連接管理和連接重試機制,以及一些擴展功能,它解決了很多ZooKeeper客戶端非常底層的細節開發工作。

主要的功能包括:連接重連、反覆註冊Watcher和NodeExistsException異常等,目前已經成爲了Apache的頂級項目,是全世界範圍內使用最廣泛的ZooKeeper客戶端之一,Patrick Hunt(ZooKeeper代碼的核心提交者)以一句 “Guava is to Java what Curator is to ZooKeeper” (Curator對於ZooKeeper,可以說就像Guava工具集對於Java平臺一樣,作用巨大)對其進行了高度評價。

除此之外,Curator中還提供了ZooKeeper各種應用場景(Recipe,如共享鎖服務、Master選舉機制和分佈式計數器等)的抽象封裝。

CuratorFramework編程特點

除了封裝一些開發人員不需要特別關注的底層細節之外,Curator還在ZooKeeper原生API的基礎上進行了包裝,提供了一套易用性和可讀性更強的Fluent風格的客戶端API框架。

CuratorFramework項目組件

  • Recipes:Zookeeper典型應用場景的實現,這些實現是基於Curator Framework。
  • Framework:Zookeeper API的高層封裝,簡化Zookeeper客戶端編程,添加了例如Zookeeper連接管理、重試機制等。
  • Utilities:爲Zookeeper提供的各種實用程序。
  • Client:Zookeeper client的封裝,用於取代原生的Zookeeper客戶端(ZooKeeper類),提供一些非常有用的客戶端特性。
  • Errors:Curator如何處理錯誤,連接問題,可恢復的例外等。

官方資源

Maven依賴說明

由以下幾個artifact的組成,但大多數情況下只用引入curator-recipes即可。

CuratorFramework簡單使用

CuratorFramework的jar包在Maven倉庫中心是可以找到 ,使用Maven,Gradle,Ant等可以很輕鬆簡單的將Curator包含到項目當中。

很多用戶會想要使用Curtor預編譯的一些工具,所以Curator提供了curator-recipes,如果你僅僅想使用Zooeeper的簡單包裝,包括鏈接管理和重試機制,那麼使用curator-framework就足夠了。

Maven依賴配置

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.12.0</version>
</dependency>

創建會話

使用CuratorFrameworkFactory這個工廠類的兩個靜態方法來創建一個客戶端:

static CuratorFramework newClient(String connectString, RetryPolicy retryPolicy);
static CuratorFramework newClient(String connectString, int sessionTimeoutMs, int connectionTimeoutMs, RetryPolicy retryPolicy);
構造方法中的各參數

  • connectString:zk的server地址,多個server之間使用英文逗號分隔開

  • connectionTimeoutMs:連接超時時間,如上是30s,默認是15s

  • sessionTimeoutMs:會話超時時間,如上是50s,默認是60s

  • retryPolicy:失敗重試策略

Session會話超時

該方法配置重連retryPolicy以及回話有效時間sessionTimeoutMs,重連就是當客戶端與zookeeper 連接異常的時候,如網絡波動,斷開鏈接,支持重新連接,會話有效這個與節點的屬性有關。那麼zookeeper 有哪些節點屬性。

重試策略

CuratorFramework通過一個接口RetryPolicy來讓用戶實現自定義的重試策略。在RetryPolicy來讓用戶實現自定義的重試策略。在RetryPolicy接口中定義了一個方法:

boolean allowRetry(int retryCount, long elapsedTimeMs, RetrySleeper sleeper)
RetryPolicy接口參數

默認提供了以下實現,分別爲ExponentialBackoffRetry、BoundedExponentialBackoffRetry、RetryForever、RetryNTimes、RetryOneTime、RetryUntilElapsed。

通過調用CuratorFramework中的start()方法來啓動會話。

獲取Zookeeper連接會話

Curator鏈接實例(CuratorFramework)由CuratorFrameworkFactory獲取,對於一個Zk集羣,僅僅需要一個CuratorFramework實例:

RetryPolicy retryPolicy  = new ExponentialBackoffRetry(1000,3);
CuratorFramework Client = CuratorFrameworkFactory.builder()
            .connectString("ip:2181,ip2:2181,ip3:2181")
            .sessionTimeoutMs(3000)
            .connectionTimeoutMs(5000)
            .retryPolicy(retryPolicy)
            .build();
client.start();
client.blockUntilConnected();

這將會使用默認的值創建一個到ZK集羣的鏈接,唯一需要特別指定單參數是重試機制,從例子上看,你需要使用:

RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); 
CuratorFramework client = CuratorFrameworkFactory.newClient(zookeeperConnectionString, retryPolicy);
client.start();

獲得到的CuratorFramework實例在使用之前需要調用其start方法,在不許要使用的時候需要調用close方法。

在上面這個示例程序中,我們首先創建了一個名爲ExponentialBackoffRetry的重試策略,該重試策略是Curator默認提供的幾種重試策略之一,其構造方法如下:

ExponentialBackOffRetry(int baseSleepTimeMs, int maxRetries);
ExponentialBackOffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs);

ExponentialBackoffRetry構造方法參數:

構造器含有三個參數 ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs)

  • baseSleepTimeMs:初始的sleep時間,用於計算之後的每次重試的sleep時間,計算公式:當前sleep時間=baseSleepTimeMs*Math.max(1, random.nextInt(1<<(retryCount+1)))

  • maxRetries:最大重試次數

  • maxSleepMs:最大sleep時間,如果上述的當前sleep計算出來比這個大,那麼sleep用這個時間

org.apache.curator.RetryPolicy接口

  • start() 開始創建會話。

  • blockUntilConnected() 直到連接成功或超時。

ExponentialBackoffRetry的重試策略

給定一個初始sleep時間baseSleepTimeMs,在這個基礎上結合重試次數,通過以下公式計算出當前需要sleep的時間:
當前sleep時間 = baseSleepTimeMs * Math.max(1, random.nextInt(1 << (retryCount + 1)))

隨着重試次數的增加,計算出的sleep時間會越來越大。如果該sleep時間在maxSleepMs的範圍之內,那麼就使用該sleep時間,否則使用maxSleepMs。另外,maxRetries參數控制了最大重試次數,以避免無限制的重試。

CuratorFrameworkFactory工廠在創建出一個客戶端CuratorFramework實例之後,實質上並沒有完成會話的創建,而是需要調用CuratorFramework的start()方法來完成會話的創建。

創建一個初始內容爲空的節點

一旦你擁有了CuratorFramework實例,你可以直接調用Zookeeper,這類似ZK發佈版本中提供的原生的ZooKeeper對象,

client.create().forPath(path);
創建一個包含內容的節點
client.create().forPath(path,"數據欸日".getBytes());
創建臨時節點,並遞歸創建父節點
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path);

此處Curator和ZkClient一樣封裝了遞歸創建父節點的方法。在遞歸創建父節點時,父節點爲持久節點。

client.create().forPath("/my/path", myData)

刪除節點

刪除一個子節點
client.delete().forPath(path);
刪除節點並遞歸刪除其子節點
client.delete().deletingChildrenIfNeeded().forPath(path);
指定版本進行刪除
client.delete().withVersion(1).forPath(path);

//如果版本不存在,則刪除異常,信息如下:

org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for
強制保證刪除一個節點
client.delete().guaranteed().forPath(path);

讀取數據

讀取節點數據內容API相當簡單,Curator提供了傳入一個Stat,使用節點當前的Stat替換到傳入的Stat的方法,查詢方法執行完成之後,Stat引用已經執行當前最新的節點Stat。

普通查詢
client.getData().forPath(path);
包含狀態查詢
Stat stat = new Stat();
client.getData().storingStatIn(stat()).forPath(path);

更新數據

更新數據,如果未傳入version參數,那麼更新當前最新版本,如果傳入version則更新指定version,如果version已經變更,則拋出異常。

普通更新
client.setData().forPath(path,"新內容".getBytes());
指定版本更新
client.setData().withVersion(1).forPath(path);

更新出錯,版本不一致異常:

org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for

異步接口

在使用以上針對節點的操作API時,我們會發現每個接口都有一個inBackground()方法可供調用。此接口就是Curator提供的異步調用入口。對應的異步處理接口爲BackgroundCallback。此接口指提供了一個processResult的方法,用來處理回調結果。其中processResult的參數event中的getType()包含了各種事件類型,getResultCode()包含了各種響應碼。

重點說一下inBackground的以下接口:

public T inBackground(BackgroundCallback callback, Executor executor);
//此接口就允許傳入一個Executor實例,用一個專門線程池來處理返回結果之後的業務邏輯。
/**
*  異步創建節點
*
* 注意:如果自己指定了線程池,那麼相應的操作就會在線程池中執行,如果沒有指定,
* 那麼就會使用Zookeeper的EventThread線程對事件進行串行處理
* */
client.create().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
    public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
        System.out.println("當前線程:" + Thread.currentThread().getName() + ",code:"
                        + event.getResultCode() + ",type:" + event.getType());
        }
    }, Executors.newFixedThreadPool(10)).forPath("/async-node01");
client.create().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
    public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
        System.out.println("當前線程:" + Thread.currentThread().getName() + ",code:" 
                           + event.getResultCode() + ",type:" + event.getType());
        }
    }).forPath("/async-node02");

創建含隔離命名空間的會話

使用Curator的好處是Curator幫助我們管理客戶端到ZK的鏈接,並且在出現網絡鏈接的問題的時候將會執行指定的重試機制。爲了實現不同的ZooKeeper業務之間的隔離,往往會爲每個業務分配一個獨立的命名空間,即指定一個ZooKeeper根路徑。

下面所示的代碼片段中定義了某一個客戶端的獨立命名空間爲/base,那麼該客戶端對ZooKeeper上數據節點的任何操作,都是基於該相對目錄進行的:

CuratorFrameworkFactory.builder().connectString("domain1.book.zookeeper:2181")
	.sessionTimeoutMs(5000).retryPolicy(retryPolicy).namespace("base").build();

參考資料

https://www.cnblogs.com/a-du/p/9892108.html

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