簡介
curator是Netflix公司開源的一個Zookeeper客戶端框架,curator框架在Zookeeper原生的API接口上進行了封裝,屏蔽了Zookeeper原生客戶端非常底層的細節開發,使得使用更加方便。並且還提供了Zookeeper多種應用場景的封裝,比如分佈式鎖服務、集羣領導選舉等場景實現的封裝,還實現了Fluent風格的鏈式調用API,是最好用,最流行的Zookeeper客戶端。
原生Zookeeper的不足:
- 連接對象異步創建,需要開發人員自行編碼等待。
- 連接沒有會話超時自動重連機制。
- Watcher一次註冊只生效一次。
- 不支持遞歸創建樹形節點。
curator特點:
- 設有Session超時重連機制。
- Watcher重複註冊機制。
- 簡化開發API。
- 遵循fluent風格API。
- 提供Zookeeper常用的場景封裝實現。
依賴:
<!--這個包是curator提供的對各種常用場景的封裝實現,比如分佈式鎖、集羣master選舉等-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<!--這個包是curator提供對Zookeeper的基本操作封裝-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
實戰demo:
curator對Zookeeper的基本操作是通過CuratorFramework接口來定義的。
curator會話連接超時重試策略
curator提供了會話連接超時的重試策略,用RetryPolicy接口定義。
常用的會話連接超時重試策略實現有:
//重試N次,當會話超時出現後,curator會每間隔sleepMsBetweenRetries毫秒時間重試一次,共重試n次。
RetryNTimes(int n, int sleepMsBetweenRetries)
//重試一次,是RetryNTimes的一種特殊實現,相當於RetryNTimes(1, int sleepMsBetweenRetries),
// 會話超時後sleepMsBetweenRetry毫秒後進行一次連接重試,僅重試一次。
public RetryOneTime(int sleepMsBetweenRetry)
//在maxElapsedTimeMs毫秒時間內,每隔sleepMsBetweenRetries重試一次。
//比如maxElapsedTimeMs=10000 sleepMsBetweenRetries=3000 ,表示在10秒內,每隔3秒重試一次,理論上是重試3次。
//重試次數有上限,是int類型的最大值次數。
public RetryUntilElapsed(int maxElapsedTimeMs, int sleepMsBetweenRetries)
//衰減重試,baseSleepTimeMs是基礎衰減時間,maxRetries是最大重試次數。
//重試時間間隔 = baseSleepTimeMs*math.max(1,random.nextInt(1<<(retryCount+1)))
//retryCount從0開始
//比如ExponentialBackoffRetry(1000,5),
//那麼第一次的重試時間間隔=1000*math.max(1,random.nextInt(1<<(1))),重試的時間間隔隨着重試的次數增大而增大,衰減重試,但是retryCount有最大值,就是當retryCount>29時,curator會把他設置成29。因爲1最多左移30位。最大間隔時間也有限制,默認int的最大值,也可以通過另一個構造函數來設置。
//只會重試maxRetries次。
public ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries)
//maxSleepMs最大間隔時間,當衰減重試計算的間隔時間比它大的,就設置爲該時間間隔。
public ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs)
創建客戶端會話,實際上就是創建CuratorFramework對象。有兩種創建風格,普通風格跟fluent風格。
public class CuratorSessionDemo{
//Zookeeper服務器地址,集羣的話多個用逗號分隔
private static final String SERVER_STRING = "192.168.18.137:2181";
//父級節點路徑
private static final String PREFIX_PATH = "curator";
private static RetryPolicy retryOneTimePolicy = new RetryOneTime(3000);
private static RetryPolicy retryThreeTimePolicy = new RetryNTimes(3,3000);
private static RetryPolicy retryUntilElapsed = new RetryUntilElapsed(10000,3000);
private static RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000,3);
private static List<AuthInfo> authInfoList = new ArrayList<>();
static {
AuthInfo authInfo = new AuthInfo("digest","admin:123456".getBytes());
authInfoList.add(authInfo);
}
@Test
public CuratorFramework getClientWithNormalStyle(){
/**
* 普通方式創建Zookeeper的curator客戶端
*參數一 Zookeeper服務器地址,多個用逗號分隔
* 參數二 客戶端的會話超時 單位毫秒
* 參數三: 客戶端連接到服務器的連接超時時間 單位毫秒
* 參數四: 會話超時重連策略
*/
CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(SERVER_STRING,5000,
10000,exponentialBackoffRetry);
//上面僅創建了客戶端。必須要使用start方法來進行連接
curatorFramework.start();
return curatorFramework;
}
public CuratorFramework getClientWithFluentStyle(){
CuratorFramework curatorFramework =
//創建一個curatorFramework構造器
CuratorFrameworkFactory.builder()
//設置Zookeeper服務器地址,多個用逗號分隔
.connectString(SERVER_STRING)
//設置連接超時時間
.connectionTimeoutMs(10000)
//設置會話超時時間
.sessionTimeoutMs(5000)
//設置數據壓縮的壓縮器
.compressionProvider(new GzipCompressionProvider())
//設置會話超時重連策略
.retryPolicy(exponentialBackoffRetry)
//添加認證
.authorization(authInfoList)
//設置該客戶端是否只進行非事務操作
.canBeReadOnly(false)
//設置默認的數據,當創建節點時不提供節點數據的話,就使用該數據
.defaultData("".getBytes())
//設置節點路徑的父節點路徑
.namespace(PREFIX_PATH)
//構建curatorFramework對象
.build();
//上面僅創建了客戶端。必須要使用start方法來進行連接
curatorFramework.start();
return curatorFramework;
}
}
常用API(不包括Watcher機制):
public class CuratorOperationDemo {
//同步創建節點
@Test
public void createNodeSync() throws Exception {
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
Stat stat = new Stat();
//返回創建節點的路徑
String path = curatorFramework
//創建節點構建器CreateBuilder
.create()
//把創建的節點的元信息保存在stat對象中
.storingStatIn(stat)
//遞歸創建
.creatingParentsIfNeeded()
//創建節點的類型,這裏選的是創建持久化節點。
.withMode(CreateMode.PERSISTENT)
//設置節點權限
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
//創建節點的路徑和數據
.forPath("/node1", "node1".getBytes());
System.out.println(path);
curatorFramework.close();
}
//異步創建節點
@Test
public void createNodeASync() throws Exception {
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
//創建一個固定的線程數的線程池
Executor executor = Executors.newFixedThreadPool(1);
Stat stat = new Stat();
//返回創建節點的路徑
String path = curatorFramework
//創建節點構建器CreateBuilder
.create()
//把創建的節點的元信息保存在stat對象中
.storingStatIn(stat)
//遞歸創建
.creatingParentsIfNeeded()
//創建節點的類型,這裏選的是創建持久化節點。
.withMode(CreateMode.PERSISTENT)
//設置節點權限
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
.inBackground(new BackgroundCallback() {
@Override
/**
* 節點創建操作成功的回調函數
* client 客戶端
* event 事件上下文
*/
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println("path = " + event.getPath());
System.out.println("context = " + event.getContext());
System.out.println("type = " + event.getType());
}
},"刪除",executor)
//創建節點的路徑和數據
.forPath("/node3", "node3".getBytes());
System.out.println(path);
TimeUnit.SECONDS.sleep(10);
curatorFramework.close();
}
//設置節點數據
@Test
public void setNodeData() throws Exception {
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
//返回設置節點後的節點元信息
Stat stat = curatorFramework.setData()
//對數據進行壓縮在設置,如果這裏設置了壓縮,獲取節點數據時就要進行相應的解壓縮操作。
.compressed()
.withVersion(-1)
.forPath("/node3", "node33333".getBytes());
System.out.println(stat);
curatorFramework.close();
}
//刪除節點
@Test
public void deleteNode() throws Exception{
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
curatorFramework.delete()
//遞歸刪除子節點
.deletingChildrenIfNeeded()
.withVersion(-1)
.forPath("/node3");
curatorFramework.close();
}
//獲取節點數據
@Test
public void getNodeData() throws Exception{
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
Stat stat = new Stat();
//返回數據
byte[] bytes = curatorFramework.getData()
//解壓縮,如果設置數據時使用了壓縮,獲取數據時要進行相應的解壓縮進行數據還原
.decompressed()
//獲取節點元信息存在stat對象中
.storingStatIn(stat)
//添加監聽器
.usingWatcher(new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println(watchedEvent.getPath());
}
})
.forPath("/node3");
curatorFramework.close();
}
//獲取和設置節點的權限列表
@Test
public void aclDemo() throws Exception {
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
//設置acl
Stat stat = curatorFramework.setACL()
.withVersion(-1)
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
.forPath("/node3");
//獲取節點acl
List<ACL> acls = curatorFramework.getACL().forPath("/node3");
System.out.println(acls);
curatorFramework.close();
}
//獲取子節點列表
@Test
public void getNodeChildren() throws Exception {
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
List<String> children = curatorFramework.getChildren()
.forPath("/node3");
System.out.println(children);
curatorFramework.close();
}
//檢查節點是否存在
@Test
public void checkExist() throws Exception {
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
//返回null表示節點不存在,反正存在
Stat stat = curatorFramework.checkExists()
//當父節點不存在時創建父節點
.creatingParentsIfNeeded()
//添加監聽
.usingWatcher(new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println(watchedEvent.getPath());
}
})
.forPath("/node3");
System.out.println(stat);
curatorFramework.close();
}
}
異步操作的inBackground方法詳解:
以上的操作都有異步模式,上面在創建節點時演示了一下,其他操作也差不多。
public interface Backgroundable<T>
{
//此方法有5個重載
//啥也不設置
public T inBackground();
//提供一個上下文對象,其實這個用的也不多。
public T inBackground(Object context);
//傳入一個回調對象BackgroundCallback,會在操作執行完成後執行該回調方法。
public T inBackground(BackgroundCallback callback);
//傳入一個回調對象BackgroundCallback和一個上下文對象context,會在操作執行完成後執行該回調方法。
public T inBackground(BackgroundCallback callback, Object context);
//傳入一個回調對象BackgroundCallback和一個線程池對象,會用該線程池裏面的線程進行相關操作,比如異步創建節點是使用這個線程池中的線程異步創建的。
public T inBackground(BackgroundCallback callback, Executor executor);
//傳入一個回調器、上下文對象、線程池。
public T inBackground(BackgroundCallback callback, Object context, Executor executor);
}
//回調器
public interface BackgroundCallback
{
/**
* 回調方法,我們就是通過實現該接口複寫自己的回調方法
* client CuratorFramework 客戶端對象
* event事件上下文
*/
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception;
}
//CuratorEvent事件上下文,裏面的值不是每個事件類型都會有值,某些值只有特定的事件觸發纔會有。
public interface CuratorEvent
{
/**
* @return event type 事件類型
*/
public CuratorEventType getType();
/**
* 操作執行的返回狀態碼可以用於判斷操作有沒有執行成功,與原生API回調的rc參數一致
*/
public int getResultCode();
/**
* 節點路徑
*/
public String getPath();
/**
* 節點上下文參數,就是inBackground方法傳遞的context。
*/
public Object getContext();
/**
* 節點的元信息
*/
public Stat getStat();
/**
* 節點的數據
*/
public byte[] getData();
public String getName();
/**
* 節點的子節點
*/
public List<String> getChildren();
/**
* 節點的權限列表
*/
public List<ACL> getACLList();
public List<CuratorTransactionResult> getOpResults();
/**
* 節點的監聽事件
*/
public WatchedEvent getWatchedEvent();
}
//curator的事件類型,會在調用不同的方法就會觸發相關的事件
public enum CuratorEventType
{
/**
* create方法事件
*/
CREATE,
/**
*delete方法事件
*/
DELETE,
/**
* checkExists方法的事件
*/
EXISTS,
/**
* getData方法事件
*/
GET_DATA,
/**
* setData方法事件
*/
SET_DATA,
/**
* getChildren方法事件
*/
CHILDREN,
/**
* sync方法事件
*/
SYNC,
/**
* getACL方法事件
*/
GET_ACL,
/**
* setACL方法事件
*/
SET_ACL,
/**
* transaction方法事件
*/
TRANSACTION,
/**
* getConfig方法事件
*/
GET_CONFIG,
/**
* reconfig方法事件
*/
RECONFIG,
/**
* watched方法事件
*/
WATCHED,
/**
* watches方法事件
*/
REMOVE_WATCHES,
/**
* close方法事件
*/
CLOSING
}
curator的事務操作
curator還能進行事務操作,保證一段代碼的原子性。這是curator獨有的。
@Test
public void transactionNode() throws Exception{
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
//CuratorOp是對每個操作的抽象,這裏創建一個新增節點的操作
//transactionOp() 這個方法返回事務操作類型 目前有create 、 check 、delete、setData
CuratorOp createOp = curatorFramework.transactionOp().
.create()
.withMode(CreateMode.PERSISTENT)
.forPath("node3", "555".getBytes());
//這裏創建一個修改節點數據的操作
CuratorOp setDataOp = curatorFramework.transactionOp()
.setData()
.withVersion(-1)
.forPath("node4", "666".getBytes());
//把操作添加進事務中,這兩個操作就具有原子性了
//返回每個操作的結果封裝成CuratorTransactionResult對象
List<CuratorTransactionResult> curatorTransactionResults = curatorFramework.transaction().forOperations(createOp, setDataOp);
for (CuratorTransactionResult curatorTransactionResult:curatorTransactionResults){
System.out.println(curatorTransactionResult);
}
}
curator的watcher實現:
curator有三種watcher來做節點監聽
- PathChildrenCache:監視一個路徑下子節點的創建、刪除、節點數據的更新。
- nodecache:監視一個節點的創建、刪除、更新。
- treecache:pathcache + nodecache。緩存路徑下所有子節點的數據。
PathChildrenCachedemo
@Test
public void testPathCache() throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
//構造器有多個重載,這裏就實現參數比較多的一個
/**
* 第一個參數 客戶端對象
* 第二個參數 要節點的節點路徑
* 第三個參數 是否緩存子節點數據
* 第四個參數 告知監聽器節點數據是否是壓縮數據
* 第五個參數 用於PathChildrenCache的後臺線程的ExecutorService。此線程池應該是單線程的,否則緩存可能會看到不一致的結果。
*/
PathChildrenCache pathChildrenCache = new PathChildrenCache(curatorFramework
,"/PathChildrenCache",
true
,false
,executorService);
//啓動監聽,必須要有這一步才能實現監聽,有三種啓動模式
//PathChildrenCache.StartMode.NORMAL 異步進行緩存初始化
//PathChildrenCache.StartMode.BUILD_INITIAL_CACHE 同步進行緩存初始化 並且會rebuild監聽器緩存
//PathChildrenCache.StartMode.POST_INITIALIZED_EVENT 異步進行緩存初始化,並且會觸發PathChildrenCacheEvent.Type.INITIALIZED事件
pathChildrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
switch (event.getType()){
case CHILD_ADDED:{
System.out.println("子節點被創建了");
System.out.println(event.getData());
System.out.println(event.getInitialData());
System.out.println(event.getType());
System.out.println(event.toString());
break;
}
case CHILD_REMOVED:{
System.out.println("子節點被刪除了");
System.out.println(event.getData());
System.out.println(event.getInitialData());
System.out.println(event.getType());
System.out.println(event.toString());
break;
}
case CHILD_UPDATED:{
System.out.println("子節點被修改了");
System.out.println(event.getData());
System.out.println(event.getInitialData());
System.out.println(event.getType());
System.out.println(event.toString());
break;
}
default:
break;
}
}
});
//獲取緩存中的子節點數據
System.out.println(pathChildrenCache.getCurrentData());
//創建子節點
String path = curatorFramework.create().withMode(CreateMode.PERSISTENT).forPath("/PathChildrenCache/node4", "node4".getBytes());
TimeUnit.SECONDS.sleep(1);
//修改子節點
Stat stat = curatorFramework.setData().forPath("/PathChildrenCache/node4", "node100".getBytes());
TimeUnit.SECONDS.sleep(1);
//刪除子節點
curatorFramework.delete().forPath("/PathChildrenCache/node4");
System.out.println("okokokokokok");
//阻塞主線程
TimeUnit.SECONDS.sleep(3000);
}
結果:
NodeCache demo
@Test
public void testNodeCache() throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
/**
* 第一個參數 curator客戶端
* 第二個參數 NodeCache
* 第三個參數 節點數據是否被壓縮過
*/
NodeCache nodeCache = new NodeCache(curatorFramework,"/NodeCache5",false);
//如果爲true,就會在start前調用rebuild方法。
nodeCache.start(true);
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
//節點的創建 刪除 數據修改都會觸發,但是這裏卻沒有分事件類型
System.out.println("nodeChanged觸發");
System.out.println("節點數據修改後的結果:" + nodeCache.getCurrentData());
System.out.println("節點路徑: " + nodeCache.getPath());
}
});
curatorFramework.create().withMode(CreateMode.PERSISTENT).forPath("/NodeCache5","555".getBytes());
TimeUnit.SECONDS.sleep(1);
curatorFramework.setData().forPath("/NodeCache5","NodeCache5".getBytes());
TimeUnit.SECONDS.sleep(1);
curatorFramework.delete().forPath("/NodeCache5");
TimeUnit.SECONDS.sleep(3000);
}
結果:
TreeCache
這個是最強大的Watcher
@Test
public void testTree() throws Exception {
CuratorFramework curatorFramework = CuratorSessionDemo.getClientWithFluentStyle();
TreeCache treeCache = new TreeCache(curatorFramework,"/treeCache");
treeCache.start();
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
switch (event.getType()){
case NODE_ADDED:{
System.out.println("節點"+ event.getData().getPath() +"被創建了");
System.out.println("新增的節點的數據:" + new String(event.getData().getData()));
break;
}
case NODE_REMOVED:{
System.out.println("節點"+ event.getData().getPath() +"被刪除了");
System.out.println("刪除的節點的數據:" + new String(event.getData().getData()));
break;
}
case NODE_UPDATED:{
System.out.println("節點"+ event.getData().getPath() +"數據被修改了");
System.out.println("修改後的節點的數據:" + new String(event.getData().getData()));
break;
}
}
}
});
//創建節點
curatorFramework.create().withMode(CreateMode.PERSISTENT).forPath("/treeCache","555".getBytes());
TimeUnit.SECONDS.sleep(1);
//修改節點數據
curatorFramework.setData().forPath("/treeCache","treeCache".getBytes());
//創建節點的子節點
curatorFramework.create().withMode(CreateMode.PERSISTENT).forPath("/treeCache/treeCache1","555".getBytes());
//創建節點的子節點
curatorFramework.create().withMode(CreateMode.PERSISTENT).forPath("/treeCache/treeCache2","555".getBytes());
//創建節點的子節點的子節點
curatorFramework.create().withMode(CreateMode.PERSISTENT).forPath("/treeCache/treeCache1/treeCache1.1","555".getBytes());
//修改子節點的數據
curatorFramework.setData().forPath("/treeCache/treeCache2","treeCache2".getBytes());
TimeUnit.SECONDS.sleep(1);
//獲取本節點treeCache的數據
ChildData treeCacheData = treeCache.getCurrentData("/treeCache");
System.out.println(treeCacheData.getPath() + "節點的數據是" + new String(treeCacheData.getData()));
//獲取子節點的數據treeCache2
ChildData treeCache2Data = treeCache.getCurrentData("/treeCache/treeCache2");
System.out.println(treeCache2Data.getPath() + "節點的數據是" + new String(treeCache2Data.getData()));
//獲取子節點的子節點treeCache1.1的數據
ChildData treeCache1_1Data = treeCache.getCurrentData("/treeCache/treeCache1/treeCache1.1");
System.out.println(treeCache1_1Data.getPath() + "節點的數據是" + new String(treeCache1_1Data.getData()));
//獲取本節點的子節點
Map<String, ChildData> currentChildren = treeCache.getCurrentChildren("/treeCache");
System.out.println("節點/treeCache的子節點列表============子節點列表");
for (Map.Entry<String,ChildData> entry: currentChildren.entrySet()){
System.out.println("key = " + entry.getKey() + "data = " + new String(entry.getValue().getData()));
}
//獲取本節點的子節點的子節點列表
Map<String, ChildData> currentChildren1 = treeCache.getCurrentChildren("/treeCache/treeCache1");
System.out.println("節點/treeCache/treeCache1的子節點列表============子節點列表");
for (Map.Entry<String,ChildData> entry: currentChildren1.entrySet()){
System.out.println("key = " + entry.getKey() + "data = " + new String(entry.getValue().getData()));
}
//刪除節點/treeCache及其子節點
curatorFramework.delete().deletingChildrenIfNeeded().forPath("/treeCache");
TimeUnit.SECONDS.sleep(3000);
}
結果:
後面的刪除就不貼出來了 。
總結 :
TreeCache能夠監控 節點的新建/刪除/數據修改 、節點的子節點的新建/刪除/修改、節點的子節點的子節點的新建/刪除/修改,還有能緩存節點及其子節點、子節點的子節點到本地,實屬強大。