1.ZooKeeper常用客戶端比較
1.ZooKeeper常用客戶端
zookeeper的常用客戶端有3種,分別是:
zookeeper原生的、Apache Curator、開源的zkclient,下面分別對介紹它們:
- zookeeper自帶的客戶端是官方提供的,比較底層、使用起來寫代碼麻煩、不夠直接。
- Apache Curator是Apache的開源項目,封裝了zookeeper自帶的客戶端,使用相對簡便,易於使用。
- zkclient是另一個開源的ZooKeeper客戶端,其地址:https://github.com/adyliu/zkclient生產環境不推薦使用。
zookeeper原生客戶端
Maven地址:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
Apache Curator的
Maven地址:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.9.0</version>
</dependency>
zkclient
的
Maven地址:
<dependency>
<groupId>com.github.adyliu</groupId>
<artifactId>zkclient</artifactId>
<version>2.1.1</version>
</dependency>
2.三種ZooKeeper客戶端比較
由於
A
pache Curator是其中比較完美的
ZooKeeper客戶端,所以主要介紹
Curator的特性來進行比較!
Curator幾個組成部分
1.
Client: 是ZooKeeper客戶端的一個替代品, 提供了一些底層處理和相關的工具方法
2.
Framework: 用來簡化ZooKeeper高級功能的使用, 並增加了一些新的功能, 比如管理到ZooKeeper集群的連接, 重試處理
3.
Recipes: 實現了通用ZooKeeper的recipe, 該組件建立在Framework的基礎之上
4.
Utilities:各種ZooKeeper的工具類
5.
Errors: 異常處理, 連接, 恢復等
6.
Extensions: recipe擴展
Curator主要解決了三類問題
1.
封裝ZooKeeper client與ZooKeeper server之間的連接處理
2.
提供了一套Fluent風格的操作API
3.
提供ZooKeeper各種應用場景(recipe, 比如共享鎖服務, 集群領導選舉機制)的抽象封裝
Curator列舉的ZooKeeper使用過程中的幾個問題
- 初始化連接的問題: 在client與server之間握手建立連接的過程中,如果握手失敗,執行所有的同步方法(比如create,getData等)將拋出異常
- 自動恢復(failover)的問題: 當client與一台server的連接丟失,並試圖去連接另外一台server時, client將回到初始連接模式
- session過期的問題: 在極端情況下,出現ZooKeeper session過期,客戶端需要自己去監聽該狀態並重新創建ZooKeeper實例
- 對可恢復異常的處理:當在server端創建一個有序ZNode,而在將節點名返回給客戶端時崩潰,此時client端拋出可恢復的異常,用戶需要自己捕獲這些異常並進行重試
- 使用場景的問題:Zookeeper提供了一些標准的使用場景支持,但是ZooKeeper對這些功能的使用說明文檔很少,而且很容易用錯.在一些極端場景下如何處理,zk並沒有給出詳細的文檔說明.比如共享鎖服務,當服務器端創建臨時順序節點成功,但是在客戶端接收到節點名之前掛掉了,如果不能很好的處理這種情況,將導致死鎖
Curator主要從以下幾個方面降低了zk使用的復雜性
1.
重試機制:提供可插拔的重試機制, 它將給捕獲所有可恢復的異常配置一個重試策略,並且內部也提供了幾種標准的重試策略(比如指數補償)
2.
連接狀態監控: Curator初始化之后會一直的對zk連接進行監聽, 一旦發現連接狀態發生變化, 將作出相應的處理
3.
zk客戶端實例管理:Curator對zk客戶端到server集群連接進行管理.並在需要的情況, 重建zk實例,保證與zk集群的可靠連接
4.
各種使用場景支持:Curator實現zk支持的大部分使用場景支持(甚至包括zk自身不支持的場景),這些實現都遵循了zk的最佳實踐,並考慮了各種極端情況
Curator聲稱的一些亮點
1.日志工具
- 內部采用SLF4J 來輸出日志
- 采用驅動器(driver)機制, 允許擴展和定制日志和跟蹤處理
- 提供了一個TracerDriver接口, 通過實現addTrace()和addCount()接口來集成用戶自己的跟蹤框架
2.和Curator相比, 另一個ZooKeeper客戶端——zkClient的不足之處
- 文檔幾乎沒有
- 異常處理弱爆了(簡單的拋出RuntimeException)
- 重試處理太難用了
- 沒有提供各種使用場景的實現
3.
對ZooKeeper自帶客戶端(ZooKeeper類)的"抱怨"
- 只是一個底層實現
- 要用需要自己寫大量的代碼
- 很容易誤用
- 需要自己處理連接丟失, 重試等
2.ZooKeeper原生客戶端基本使用
1.ZooKeeper自帶客戶端API介紹
ZooKeeper自帶客戶端的主要類是ZooKeeper類,下面介紹
ZooKeeper API的使用:
ZooKeeper類的構造方法如下:
從上圖可知創建
ZooKeeper類對象除了需要
ZooKeeper服務端連接字符串(IP地址:端口)
,還必須提供一個Watcher對象。Watcher是一個接口,當服務器節點花發生變化就會以事件的形式通知
Watcher對象。所以
Watcher常用來監聽節點,當節點發生變化時客戶端就會知道。
ZooKeeper類還有對節點進行增刪改的操作方法,主要方法如下:
create:用於創建節點,可以指定節點路徑、節點數據、節點的訪問權限、節點類型
delete:刪除節點,每個節點都有一個版本,刪除時可指定刪除的版本,類似樂觀鎖。設置-1,則就直接刪除節點。
exists:節點存不存在,若存在返回節點Stat信息,否則返回null
getChildren:獲取子節點
getData/setData:獲取節點數據
getACL/setACL:獲取節點訪問權限列表,每個節點都可以設置訪問權限,指定只有特定的客戶端才能訪問和操作節點。
在這些方法中的
boolean watch參數表示是否監聽當前節點(使用的監聽
Watcher就是構造
ZooKeeper對象時的
watcher
),而方法中的
Watcher watcher參數則是使用指定的
watcher來監聽當前節點。
ZooKeeper類所有的方法如下圖:
2.ZooKeeper自帶客戶端的簡單操作示例
public static void main(String[] args) throws IOException, KeeperException, InterruptedException
{
// 創建一個與服務器的連接 需要(服務端的 ip+端口號)(session過期時間)(Watcher監聽注冊)
ZooKeeper zk = new ZooKeeper("192.168.110.100:2181", 3000, new Watcher()
{
// 監控所有被觸發的事件
public void process(WatchedEvent event)
{
System.out.println(event.toString());
}
});
System.out.println("OK!");
// 創建一個目錄節點
/**
* CreateMode:
* PERSISTENT (持續的,相對於EPHEMERAL,不會隨着client的斷開而消失)
* PERSISTENT_SEQUENTIAL(持久的且帶順序的)
* EPHEMERAL (短暫的,生命周期依賴於client session)
* EPHEMERAL_SEQUENTIAL (短暫的,帶順序的)
*/
zk.create("/path01", "data01".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 創建一個子目錄節點
zk.create("/path01/path01", "data01/data01".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(new String(zk.getData("/path01", false, null)));
// 取出子目錄節點列表
System.out.println(zk.getChildren("/path01", true));
// 創建另外一個子目錄節點
zk.create("/path01/path02", "data01/data02".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(zk.getChildren("/path01", true));
// 修改子目錄節點數據
zk.setData("/path01/path01", "data01/data01-01".getBytes(), -1);
byte[] datas = zk.getData("/path01/path01", true, null);
String str = new String(datas, "utf-8");
System.out.println(str);
// 刪除整個子目錄 -1代表version版本號,-1是刪除所有版本
zk.delete("/path01/path01", -1);
zk.delete("/path01/path02", -1);
zk.delete("/path01", -1);
System.out.println(str);
Thread.sleep(15000);
zk.close();
System.out.println("OK");
}
節點類型說明:
節點類型有4種:“
PERSISTENT
、
PERSISTENT_SEQUENTIAL
、
EPHEMERAL
、
EPHEMERAL_SEQUENTIAL
”
其中“
EPHEMERAL
、
EPHEMERAL_SEQUENTIAL
”兩種是客戶端斷開連接(
Session無效時
)節點會被自動刪除;“
PERSISTENT_SEQUENTIAL
、
EPHEMERAL_SEQUENTIAL
”兩種是節點名后綴是一個自動增長序號。
節點訪問權限說明:
節點訪問權限由List<ACL>確定,但是有幾個便捷的靜態屬性可以選擇:
Ids.CREATOR_ALL_ACL:只有創建節點的客戶端才有所有權限
Ids.OPEN_ACL_UNSAFE:這是一個完全開放的權限,所有客戶端都有權限
Ids.READ_ACL_UNSAFE:所有客戶端只有讀取的
3.節點監聽注意點
使用
Watcher監聽時需要注意:所有的節點監聽觸發一次之后就不會再觸發,需要重新再設置監聽。但是當多個客戶端同時監聽某個節點時,要是這個節點發生變化,所有的客戶端都會被觸發!
3.Apache Curator基本使用
此處只是簡單的介紹Apache
Curator
,是能作為入門使用,后面的章節會深入介紹基於
Apache Curator實現Zookeeper的各種應用場景。在實際的工作中建議使用
Apache Curator。
1.Curator使用示例
public static void main(String[] args) throws Exception
{
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", new RetryNTimes(10, 5000));
client.start();// 連接
// 獲取子節點,順便監控子節點
List<String> children = client.getChildren().usingWatcher(new CuratorWatcher()
{
@Override
public void process(WatchedEvent event) throws Exception
{
System.out.println("監控: " + event);
}
}).forPath("/");
System.out.println(children);
// 創建節點
String result = client.create().withMode(CreateMode.PERSISTENT).withACL(Ids.OPEN_ACL_UNSAFE).forPath("/test", "Data".getBytes());
System.out.println(result);
// 設置節點數據
client.setData().forPath("/test", "111".getBytes());
client.setData().forPath("/test", "222".getBytes());
// 刪除節點
System.out.println(client.checkExists().forPath("/test"));
client.delete().withVersion(-1).forPath("/test");
System.out.println(client.checkExists().forPath("/test"));
client.close();
System.out.println("OK!");
}
CuratorFrameworkFactory工廠方法newClient()提供了一個簡單方式創建實例。 而Builder提供了更多的參數控制。一旦你創建了一個CuratorFramework實例,你必須調用它的start()啟動,在應用退出時調用close()方法關閉.
Curator框架提供了一種流式接口。 操作通過builder串聯起來, 這樣方法調用類似語句一樣。
2. CuratorFramework類重要方法說明
- create() 開始創建操作, 可以調用額外的方法(比如方式mode 或者后台執行background) 並在最后調用forPath()指定要操作的ZNode
- delete() 開始刪除操作. 可以調用額外的方法(版本或者后台處理version or background)並在最后調用forPath()指定要操作的ZNode
- checkExists() 開始檢查ZNode是否存在的操作. 可以調用額外的方法(監控或者后台處理)並在最后調用forPath()指定要操作的ZNode
- getData() 開始獲得ZNode節點數據的操作. 可以調用額外的方法(監控、后台處理或者獲取狀態watch, background or get stat) 並在最后調用forPath()指定要操作的ZNode
- setData() 開始設置ZNode節點數據的操作. 可以調用額外的方法(版本或者后台處理) 並在最后調用forPath()指定要操作的ZNode
- getChildren() 開始獲得ZNode的子節點列表。 以調用額外的方法(監控、后台處理或者獲取狀態watch, background or get stat) 並在最后調用forPath()指定要操作的ZNode
- inTransaction() 開始是原子ZooKeeper事務. 可以復合create, setData, check, and/or delete 等操作然后調用commit()作為一個原子操作提交
3.Curator事件監聽
后台操作的通知和監控可以通過ClientListener接口發布。你可以在CuratorFramework實例上通過addListener()注冊listener。
client.getCuratorListenable().addListener(new CuratorListener()
{
@Override
public void eventReceived(CuratorFramework client, CuratorEvent event) throws Exception
{
System.out.println("事件: " + event);
}
});
client.getConnectionStateListenable().addListener(new ConnectionStateListener()
{
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState)
{
System.out.println("連接狀態事件: " + newState);
}
});
client.getUnhandledErrorListenable().addListener(new UnhandledErrorListener()
{
@Override
public void unhandledError(String message, Throwable e)
{
System.out.println("錯誤事件:" + message);
}
});
其事件觸發時間說明:
- CuratorListenable:當使用后台線程操作時,后台線程執行完成就會觸發,例如:client.getData().inBackground().forPath("/test");后台獲取節點數據,獲取完成之后觸發。
- ConnectionStateListenable:當連接狀態變化時觸發。
- UnhandledErrorListenable:當后台操作發生異常時觸發。
CuratorListenable事件觸發返回的數據如下:
事件類型 事件返回數據
CREATE getResultCode() and getPath()
DELETE getResultCode() and getPath()
EXISTS getResultCode(), getPath() and getStat()
GET_DATA getResultCode(), getPath(), getStat() and getData()
SET_DATA getResultCode(), getPath() and getStat()
CHILDREN getResultCode(), getPath(), getStat(), getChildren()
SYNC getResultCode(), getStat()
GET_ACL getResultCode(), getACLList()
SET_ACL getResultCode()
TRANSACTION getResultCode(), getOpResults()
WATCHED getWatchedEvent()
GET_CONFIG getResultCode(), getData()
RECONFIG getResultCode(), getData()
4.Curator其他功能介紹
(1)命名空間
你可以使用命名空間Namespace避免多個應用的節點的名稱沖突。 CuratorFramework提供了命名空間的概念,這樣CuratorFramework會為它的API調用的path加上命名空間:
CuratorFramework client = CuratorFrameworkFactory.builder().namespace("MyApp") ... build();
(2)臨時客戶端
Curator還提供了臨時的CuratorFramework:CuratorTempFramework,一定時間不活動后連接會被關閉。創建builder時不是調用build()而是調用buildTemp()。3分鍾不活動連接就被關閉,你也可以指定不活動的時間。
CuratorTempFramework client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")// 連接串
.retryPolicy(new RetryNTimes(10, 5000))// 重試策略
.connectionTimeoutMs(100) // 連接超時
.sessionTimeoutMs(100) // 會話超時
.buildTemp(100, TimeUnit.MINUTES); // 臨時客戶端並設置連接時間
它只提供了下面幾個方法:
public void close();
public CuratorTransaction inTransaction() throws Exception;
public TempGetDataBuilder getData() throws Exception;
(3)Retry策略
retry策略可以改變retry的行為。 它抽象出RetryPolicy接口, 包含一個方法public boolean allowRetry(int retryCount, long elapsedTimeMs);。 在retry被嘗試執行前, allowRetry()被調用,並且將當前的重試次數和操作已用時間作為參數. 如果返回true, retry被執行。否則異常被拋出。
Curator本身提供了幾個策略:
- ExponentialBackoffRetry:重試一定次數,每次重試sleep更多的時間
- RetryNTimes:重試N次
- RetryOneTime:重試一次
- RetryUntilElapsed:重試一定的時間
