github: https://github.com/zhaikaishun/zookeeper_tutorial
Curator框架的目的
官網首頁介紹是 Guava is to Java what Curator is to Xookeeper ,爲了更好的實現java操作zookeeper服務器,後來出現了Curator框架,非常的強大,目前已經是Apache的頂級項目,裏面提供了更多豐富的操作,例如session超時重連、主從選舉、分佈式計算器、分佈式鎖等等適用於各種複雜zookeeper場景的API封裝。
Maven依賴
jar包下載
都去官網下載,http://curator.apache.org/
Curatot框架使用(一)
Curatir框架使用鏈式編程風格,易讀性更強,使用工程方法創建連接對象。
1 使用CuratorFrameworkFactory的兩個靜態工廠方法(參數不同)來實現:
- 參數1: connectString,連接串
- 參數2: retyPolicy,重試連接策略。有四中實現分別爲:
ExponentialBackoffRetry、RetryTimes、RetryOneTimes、RetryUntilElapsed(具體參數的意思以後會講解,也可先上網查看) - 參數3:sessionTimeoutMs 會話超時時間 默認爲60000ms
- 參數4:connectionTimeoutMs 連接超時時間,默認爲15000ms
注意:對於retryPolicy策略通過一個接口來讓用戶自定義實現。
代碼在package bjsxt.curator.base;
代碼示例
前面的設置
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181,192.168.1.33:2181";
/** session超時時間 */
static final int SESSION_OUTTIME = 5000;//ms
public static void main(String[] args) throws Exception {
//1 重試策略:初試時間爲1s 重試10次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通過工廠創建連接
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
// .namespace("super")
.build();
//3 開啓連接
cf.start();
System.out.println(States.CONNECTED);
System.out.println(cf.getState());
Curator的基本方法
1.創建連接
2.Curator創建節點
Create方法,可選鏈式項:creatingParentslfNeeded、withMode、forPath、withACL等。
例如
//4 建立節點 指定節點類型(不加withMode默認爲持久類型節點)、路徑、數據內容
cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/super/c1","c1內容".getBytes());
或者
// cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/super/c1","c1內容".getBytes());
// cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/super/c2","c2內容".getBytes());
3.刪除節點
delete方法,可選鏈式項: deletingChildrenIfNeeded、guranteed、withVersion、forPath等。
例如
cf.delete().guaranteed().deletingChildrenIfNeeded().forPath("/super");
4.讀取和修改數據
getData、setData方法
//讀取節點
String ret1 = new String(cf.getData().forPath("/super/c2"));
System.out.println(ret1);
//修改節點
cf.setData().forPath("/super/c2", "修改c2內容".getBytes());
String ret2 = new String(cf.getData().forPath("/super/c2"));
System.out.println(ret2);
5.異步回調方法。
比如創建節點時綁定一個回調函數,該回調函數可以輸出服務器的狀態碼以及服務器事件類型。還可以加入一個線程池進行優化操作。
ExecutorService pool = Executors.newCachedThreadPool();
cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
.inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework cf, CuratorEvent ce) throws Exception {
System.out.println("code:" + ce.getResultCode());
System.out.println("type:" + ce.getType());
System.out.println("線程爲:" + Thread.currentThread().getName());
}
}, pool)
.forPath("/super/c3","c3內容".getBytes());
Thread.sleep(Integer.MAX_VALUE);
6.讀取子節點方法
getChildren
List<String> list = cf.getChildren().forPath("/super");
for(String p : list){
System.out.println(p);
}
7.判斷子節點是否存在
checkExists方法
Stat stat = cf.checkExists().forPath("/super/c3");
System.out.println(stat);
講上面異步回調的那個線程池的作用
比如某個操作一次性要創建500個節點,不可能一次用500個線程去處理。所以這裏使用的是一個線程池來進行控制
CuratorWatcher
原理,使用緩存的判斷的方式,不需要重複註冊!!!最牛的地方,估計可以想到那個宕機訂閱問題。具體的原理,建議深入瞭解一下,感覺挺厲害的。
1. 方法1
注意最後一個參數,這個是是否壓縮 , 注意那個cache.star的時候的那個模式 POST_INITALLZED_EVENT
直接上代碼看即可
public class CuratorWatcher1 {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181,192.168.1.33:2181";
/** session超時時間 */
static final int SESSION_OUTTIME = 5000;//ms
public static void main(String[] args) throws Exception {
//1 重試策略:初試時間爲1s 重試10次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通過工廠創建連接
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
.build();
//3 建立連接
cf.start();
//4 建立一個cache緩存
final NodeCache cache = new NodeCache(cf, "/super", false);
cache.start(true);
cache.getListenable().addListener(new NodeCacheListener() {
/**
* <B>方法名稱:</B>nodeChanged<BR>
* <B>概要說明:</B>觸發事件爲創建節點和更新節點,在刪除節點的時候並不觸發此操作。<BR>
* @see org.apache.curator.framework.recipes.cache.NodeCacheListener#nodeChanged()
*/
@Override
public void nodeChanged() throws Exception {
System.out.println("路徑爲:" + cache.getCurrentData().getPath());
System.out.println("數據爲:" + new String(cache.getCurrentData().getData()));
System.out.println("狀態爲:" + cache.getCurrentData().getStat());
System.out.println("---------------------------------------");
}
});
Thread.sleep(1000);
cf.create().forPath("/super", "123".getBytes());
Thread.sleep(1000);
cf.setData().forPath("/super", "456".getBytes());
Thread.sleep(1000);
cf.delete().forPath("/super");
Thread.sleep(Integer.MAX_VALUE);
}
}
輸出
路徑爲:/super
數據爲:123
狀態爲:38654705677,38654705677,1509971265443,1509971265443,0,0,0,0,3,0,38654705677
---------------------------------------
路徑爲:/super
數據爲:456
狀態爲:38654705677,38654705678,1509971265443,1509971266479,1,0,0,0,3,0,38654705677
---------------------------------------
2. 方法2
注意第三個參數,表示是否接受節點數據內容,如果爲false則不接受
public class CuratorWatcher2 {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181";
/** session超時時間 */
static final int SESSION_OUTTIME = 10000;//ms
public static void main(String[] args) throws Exception {
//1 重試策略:初試時間爲1s 重試10次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通過工廠創建連接
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
.build();
//3 建立連接
cf.start();
//4 建立一個PathChildrenCache緩存,第三個參數爲是否接受節點數據內容 如果爲false則不接受
PathChildrenCache cache = new PathChildrenCache(cf, "/super", true);
//5 在初始化的時候就進行緩存監聽
cache.start(StartMode.POST_INITIALIZED_EVENT);
cache.getListenable().addListener(new PathChildrenCacheListener() {
/**
* <B>方法名稱:</B>監聽子節點變更<BR>
* <B>概要說明:</B>新建、修改、刪除<BR>
* @see org.apache.curator.framework.recipes.cache.PathChildrenCacheListener#childEvent(org.apache.curator.framework.CuratorFramework, org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent)
*/
@Override
public void childEvent(CuratorFramework cf, PathChildrenCacheEvent event) throws Exception {
switch (event.getType()) {
case CHILD_ADDED:
System.out.println("CHILD_ADDED :" + event.getData().getPath());
//也可以獲取內容
System.out.println("CHILD_ADDED 內容 :" + new String(event.getData().getData(),"utf-8"));
break;
case CHILD_UPDATED:
System.out.println("CHILD_UPDATED :" + event.getData().getPath());
System.out.println("CHILD_UPDATED 內容 :" + new String(event.getData().getData(),"utf-8"));
break;
case CHILD_REMOVED:
System.out.println("CHILD_REMOVED :" + event.getData().getPath());
break;
default:
break;
}
}
});
//創建本身節點不發生變化
cf.create().forPath("/super", "init".getBytes());
//添加子節點
Thread.sleep(1000);
cf.create().forPath("/super/c1", "c1內容".getBytes());
Thread.sleep(1000);
cf.create().forPath("/super/c2", "c2內容".getBytes());
//修改子節點
Thread.sleep(1000);
cf.setData().forPath("/super/c1", "c1更新內容".getBytes());
//刪除子節點
Thread.sleep(1000);
cf.delete().forPath("/super/c2");
//刪除本身節點
Thread.sleep(1000);
cf.delete().deletingChildrenIfNeeded().forPath("/super");
System.out.println("------end------");
Thread.sleep(Integer.MAX_VALUE);
}
}
運行結果
CHILD_ADDED :/super/c1
CHILD_ADDED 內容 :c1內容
CHILD_ADDED :/super/c2
CHILD_ADDED 內容 :c2內容
CHILD_UPDATED :/super/c1
CHILD_UPDATED 內容 :c1更新內容
CHILD_REMOVED :/super/c2
CHILD_REMOVED :/super/c1
------end------
Curator場景應用(一)
分佈式鎖功能
在分佈式場景中,我們爲了保證數據的一致性,經常在程序運行的某一個點需要進行同步操作(java可提供synchronized或者Reentrantlock實現)比如我們看一個小示例,這個示例會出現分佈式不同步的問題:
因爲我們之前所說的是再高併發下訪問一個程序,現在我們則是在高併發下訪問多個服務器節點(分佈式)
我們使用Curator基於Zookeeper的特性提供的分佈式鎖來處理分佈式場景的數據一致性,zookeeper原生的寫分佈式比較麻煩,我們這裏強烈推薦使用Curator的分佈式鎖!
Curator主要使用 InterProcessMutex 來進行分佈式鎖的控制
public class Lock2 {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181";
/** session超時時間 */
static final int SESSION_OUTTIME = 20000;//ms
static int count = 10;
public static void genarNo(){
try {
count--;
System.out.println(count);
} finally {
}
}
public static void main(String[] args) throws Exception {
//1 重試策略:初試時間爲1s 重試10次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通過工廠創建連接
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
// .namespace("super")
.build();
//3 開啓連接
cf.start();
//4 分佈式鎖
final CountDownLatch countdown = new CountDownLatch(1);
for(int i = 0; i < 10; i++){
new Thread(new Runnable() {
@Override
public void run() {
InterProcessMutex lock = new InterProcessMutex(cf, "/super");
try {
countdown.await();
//加鎖
lock.acquire();
//-------------業務處理開始
genarNo();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss|SSS");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+"執行此操作");
//-------------業務處理結束
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//釋放
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
},"t" + i).start();
}
Thread.sleep(100);
countdown.countDown();
}
}
輸出
9
t9執行此操作
8
t8執行此操作
7
t3執行此操作
6
t0執行此操作
5
t6執行此操作
4
t7執行此操作
3
t2執行此操作
2
t5執行此操作
1
t4執行此操作
0
t1執行此操作
我們可以看到,這裏new了10個線程,但是每個線程裏面都有各自的鎖,按照道理來說,他們各部干擾,但是從結果可以看出來,這個程序還是同步的,也實現了鎖的原理。(相當於不同的程序放在不同的機器上,也有類似的效果)。
分佈式計數器功能
一說到分佈式計數器,你可能腦海裏想到了AtomicInteger這種經典的方式,如果針對一個jvm的場景當然沒有問題,但是我們現在是在分佈式場景下,就需要利用Curator框架的DistributedAtomicInteger了
代碼
public class CuratorAtomicInteger {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181";
/** session超時時間 */
static final int SESSION_OUTTIME = 5000;//ms
public static void main(String[] args) throws Exception {
//1 重試策略:初試時間爲1s 重試10次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通過工廠創建連接
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
.build();
//3 開啓連接
cf.start();
//cf.delete().forPath("/super");
//4 使用DistributedAtomicInteger
DistributedAtomicInteger atomicIntger =
new DistributedAtomicInteger(cf, "/super", new RetryNTimes(3, 1000));
//atomicIntger.forceSet(0); //第一次需要有吧?
AtomicValue<Integer> value = atomicIntger.add(1);
// atomicIntger.increment();
// AtomicValue<Integer> value = atomicIntger.get();
System.out.println(value.succeeded());
System.out.println(value.postValue()); //最新值
System.out.println(value.preValue()); //原始值
}
}
第一次運行
true
1
0
第二次運行
true
2
1
第三次運行
true
3
2
其實這也就模擬了分佈式的計數功能
barrier功能
有這樣的場景,多個程序在不同的機器中,需要等待同時都準備好了,再一起運行
有兩種方式,一種是等所有的都準備好在一起跑,一種是有一個開關,這個開關打開就跑,直接看代碼
方式一: 估計不常用
public class CuratorBarrier1 {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181";
/** session超時時間 */
static final int SESSION_OUTTIME = 5000;//ms
public static void main(String[] args) throws Exception {
for(int i = 0; i < 5; i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.retryPolicy(retryPolicy)
.build();
cf.start();
DistributedDoubleBarrier barrier = new DistributedDoubleBarrier(cf, "/super", 5);
Thread.sleep(1000 * (new Random()).nextInt(3));
System.out.println(Thread.currentThread().getName() + "已經準備");
barrier.enter();
System.out.println("同時開始運行...");
Thread.sleep(1000 * (new Random()).nextInt(3));
System.out.println(Thread.currentThread().getName() + "運行完畢");
barrier.leave();
System.out.println("同時退出運行...");
} catch (Exception e) {
e.printStackTrace();
}
}
},"t" + i).start();
}
}
}
運行結果
t4已經準備
t2已經準備
t0已經準備
t1已經準備
t3已經準備
同時開始運行...
同時開始運行...
t2運行完畢
同時開始運行...
t4運行完畢
同時開始運行...
t0運行完畢
同時開始運行...
t3運行完畢
t1運行完畢
同時退出運行...
同時退出運行...
同時退出運行...
同時退出運行...
同時退出運行...
方式二: 可能用的多一些,切近實際一些
代碼如下
public class CuratorBarrier2 {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181";
/** session超時時間 */
static final int SESSION_OUTTIME = 50000;//ms
static DistributedBarrier barrier = null;
public static void main(String[] args) throws Exception {
for(int i = 0; i < 5; i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
.build();
cf.start();
barrier = new DistributedBarrier(cf, "/super");
System.out.println(Thread.currentThread().getName() + "設置barrier!");
barrier.setBarrier(); //設置
barrier.waitOnBarrier(); //等待
System.out.println("---------開始執行程序----------");
} catch (Exception e) {
e.printStackTrace();
}
}
},"t" + i).start();
}
Thread.sleep(5000);
barrier.removeBarrier(); //釋放
}
}
運行結果
t0設置barrier!
t4設置barrier!
t2設置barrier!
t3設置barrier!
t1設置barrier!
---------開始執行程序----------
集羣的功能
管理配置等
注意,這個之後訂閱後,宕機後再次打開,也會接受節點變更的信號。 我估計是由於緩存? 不太明白,具體再看了。
TODO
本文大多來自於筆記,好記性不如爛筆頭,爛筆頭這年頭比不上雲筆記了