閱讀文本大概需要3分鐘。
source:https://www.cnblogs.com/erbing/p/9799098.html
一 簡介
Apache Curator是一個比較完善的ZooKeeper客戶端框架,通過封裝的一套高級API 簡化了ZooKeeper的操作。通過查看官方文檔,可以發現Curator主要解決了三類問題:
封裝ZooKeeper client與ZooKeeper server之間的連接處理
提供了一套Fluent風格的操作API
提供ZooKeeper各種應用場景(recipe, 比如:分佈式鎖服務、集羣領導選舉、共享計數器、緩存機制、分佈式隊列等)的抽象封裝
Curator主要從以下幾個方面降低了zk使用的複雜性:
重試機制:提供可插拔的重試機制, 它將給捕獲所有可恢復的異常配置一個重試策略,並且內部也提供了幾種標準的重試策略(比如指數補償)
連接狀態監控: Curator初始化之後會一直對zk連接進行監聽,一旦發現連接狀態發生變化將會作出相應的處理
zk客戶端實例管理:Curator會對zk客戶端到server集羣的連接進行管理,並在需要的時候重建zk實例,保證與zk集羣連接的可靠性
各種使用場景支持:Curator實現了zk支持的大部分使用場景(甚至包括zk自身不支持的場景),這些實現都遵循了zk的最佳實踐,並考慮了各種極端情況
二 基於Curator的ZooKeeper基本用法
public class CuratorBase {
//會話超時時間
private final int SESSION_TIMEOUT = 30 * 1000;
//連接超時時間
private final int CONNECTION_TIMEOUT = 3 * 1000;
//ZooKeeper服務地址
private static final String CONNECT_ADDR = "192.168.1.1:2100,192.168.1.1:2101,192.168.1.:2102";
//創建連接實例
private CuratorFramework client = null;
public static void main(String[] args) throws Exception {
//1 重試策略:初試時間爲1s 重試10次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通過工廠創建連接
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR).connectionTimeoutMs(CONNECTION_TIMEOUT)
.sessionTimeoutMs(SESSION_TIMEOUT)
.retryPolicy(retryPolicy)
//命名空間 .namespace("super")
.build();
//3 開啓連接
cf.start();
System.out.println(States.CONNECTED);
System.out.println(cf.getState());
//創建永久節點
client.create().forPath("/curator","/curator data".getBytes());
//創建永久有序節點
client.create().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath("/curator_sequential","/curator_sequential data".getBytes());
//創建臨時節點
client.create().withMode(CreateMode.EPHEMERAL)
.forPath("/curator/ephemeral","/curator/ephemeral data".getBytes());
//創建臨時有序節點
client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL) .forPath("/curator/ephemeral_path1","/curator/ephemeral_path1 data".getBytes());
client.create().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/curator/ephemeral_path2","/curator/ephemeral_path2 data".getBytes());
//測試檢查某個節點是否存在
Stat stat1 = client.checkExists().forPath("/curator");
Stat stat2 = client.checkExists().forPath("/curator2");
System.out.println("'/curator'是否存在: " + (stat1 != null ? true : false));
System.out.println("'/curator2'是否存在: " + (stat2 != null ? true : false));
//獲取某個節點的所有子節點
System.out.println(client.getChildren().forPath("/"));
//獲取某個節點數據
System.out.println(new String(client.getData().forPath("/curator")));
//設置某個節點數據
client.setData().forPath("/curator","/curator modified data".getBytes());
//創建測試節點
client.create().orSetData().creatingParentContainersIfNeeded()
.forPath("/curator/del_key1","/curator/del_key1 data".getBytes());
client.create().orSetData().creatingParentContainersIfNeeded()
.forPath("/curator/del_key2","/curator/del_key2 data".getBytes());
client.create().forPath("/curator/del_key2/test_key","test_key data".getBytes());
//刪除該節點
client.delete().forPath("/curator/del_key1");
//級聯刪除子節點
client.delete().guaranteed().deletingChildrenIfNeeded().forPath("/curator/del_key2");
}
}
orSetData()方法:如果節點存在則Curator將會使用給出的數據設置這個節點的值,相當於 setData() 方法
creatingParentContainersIfNeeded()方法:如果指定節點的父節點不存在,則Curator將會自動級聯創建父節點
guaranteed()方法:如果服務端可能刪除成功,但是client沒有接收到刪除成功的提示,Curator將會在後臺持續嘗試刪除該節點
deletingChildrenIfNeeded()方法:如果待刪除節點存在子節點,則Curator將會級聯刪除該節點的子節點
事務管理:
/**
* 事務管理:碰到異常,事務會回滾
* @throws Exception
*/
@Test
public void testTransaction() throws Exception{
//定義幾個基本操作
CuratorOp createOp = client.transactionOp().create()
.forPath("/curator/one_path","some data".getBytes());
CuratorOp setDataOp = client.transactionOp().setData()
.forPath("/curator","other data".getBytes());
CuratorOp deleteOp = client.transactionOp().delete()
.forPath("/curator");
//事務執行結果
List<CuratorTransactionResult> results = client.transaction()
.forOperations(createOp,setDataOp,deleteOp);
//遍歷輸出結果
for(CuratorTransactionResult result : results){
System.out.println("執行結果是: " + result.getForPath() + "--" + result.getType());
}
}
//因爲節點“/curator”存在子節點,所以在刪除的時候將會報錯,事務回滾
三 監聽器
Curator提供了三種Watcher(Cache)來監聽結點的變化:
Path Cache:監視一個路徑下1)孩子結點的創建、2)刪除,3)以及結點數據的更新。產生的事件會傳遞給註冊的PathChildrenCacheListener。
Node Cache:監視一個結點的創建、更新、刪除,並將結點的數據緩存在本地。
Tree Cache:Path Cache和Node Cache的“合體”,監視路徑下的創建、更新、刪除事件,並緩存路徑下所有孩子結點的數據。
/**
* 在註冊監聽器的時候,如果傳入此參數,當事件觸發時,邏輯由線程池處理
*/
ExecutorService pool = Executors.newFixedThreadPool(2);
/**
* 監聽數據節點的變化情況
*/
final NodeCache nodeCache = new NodeCache(client, "/zk-huey/cnode", false);
nodeCache.start(true);
nodeCache.getListenable().addListener(
new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println("Node data is changed, new data: " +
new String(nodeCache.getCurrentData().getData()));
}
},
pool
);
/**
* 監聽子節點的變化情況
*/
final PathChildrenCache childrenCache = new PathChildrenCache(client, "/zk-huey", true);
childrenCache.start(StartMode.POST_INITIALIZED_EVENT);
childrenCache.getListenable().addListener(
new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
throws Exception {
switch (event.getType()) {
case CHILD_ADDED:
System.out.println("CHILD_ADDED: " + event.getData().getPath());
break;
case CHILD_REMOVED:
System.out.println("CHILD_REMOVED: " + event.getData().getPath());
break;
case CHILD_UPDATED:
System.out.println("CHILD_UPDATED: " + event.getData().getPath());
break;
default:
break;
}
}
},
pool
);
client.setData().forPath("/zk-huey/cnode", "world".getBytes());
Thread.sleep(10 * 1000);
pool.shutdown();
client.close();
四 分佈式鎖
分佈式編程時,比如最容易碰到的情況就是應用程序在線上多機部署,於是當多個應用同時訪問某一資源時,就需要某種機制去協調它們。例如,現在一臺應用正在rebuild緩存內容,要臨時鎖住某個區域暫時不讓訪問;又比如調度程序每次只想一個任務被一臺應用執行等等。
下面的程序會啓動兩個線程t1和t2去爭奪鎖,拿到鎖的線程會佔用5秒。運行多次可以觀察到,有時是t1先拿到鎖而t2等待,有時又會反過來。Curator會用我們提供的lock路徑的結點作爲全局鎖,這個結點的數據類似這種格式:[_c_64e0811f-9475-44ca-aa36-c1db65ae5350-lock-0000000005],每次獲得鎖時會生成這種串,釋放鎖時清空數據。
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.RetryNTimes;
import java.util.concurrent.TimeUnit;
/**
* Curator framework's distributed lock test.
*/
public class CuratorDistrLockTest {
/** Zookeeper info */
private static final String ZK_ADDRESS = "192.168.1.100:2181";
private static final String ZK_LOCK_PATH = "/zktest";
public static void main(String[] args) throws InterruptedException {
// 1.Connect to zk
CuratorFramework client = CuratorFrameworkFactory.newClient(
ZK_ADDRESS,
new RetryNTimes(10, 5000)
);
client.start();
System.out.println("zk client start successfully!");
Thread t1 = new Thread(() -> {
doWithLock(client);
}, "t1");
Thread t2 = new Thread(() -> {
doWithLock(client);
}, "t2");
t1.start();
t2.start();
}
private static void doWithLock(CuratorFramework client) {
InterProcessMutex lock = new InterProcessMutex(client, ZK_LOCK_PATH);
try {
if (lock.acquire(10 * 1000, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " hold lock");
Thread.sleep(5000L);
System.out.println(Thread.currentThread().getName() + " release lock");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
五 Leader選舉
當集羣裏的某個服務down機時,我們可能要從slave結點裏選出一個作爲新的master,這時就需要一套能在分佈式環境中自動協調的Leader選舉方法。Curator提供了LeaderSelector監聽器實現Leader選舉功能。同一時刻,只有一個Listener會進入takeLeadership()方法,說明它是當前的Leader。注意:當Listener從takeLeadership()退出時就說明它放棄了“Leader身份”,這時Curator會利用Zookeeper再從剩餘的Listener中選出一個新的Leader。autoRequeue()方法使放棄Leadership的Listener有機會重新獲得Leadership,如果不設置的話放棄了的Listener是不會再變成Leader的。
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.retry.RetryNTimes;
import org.apache.curator.utils.EnsurePath;
/**
* Curator framework's leader election test.
* Output:
* LeaderSelector-2 take leadership!
* LeaderSelector-2 relinquish leadership!
* LeaderSelector-1 take leadership!
* LeaderSelector-1 relinquish leadership!
* LeaderSelector-0 take leadership!
* LeaderSelector-0 relinquish leadership!
* ...
*/
public class CuratorLeaderTest {
/** Zookeeper info */
private static final String ZK_ADDRESS = "192.168.1.100:2181";
private static final String ZK_PATH = "/zktest";
public static void main(String[] args) throws InterruptedException {
LeaderSelectorListener listener = new LeaderSelectorListener() {
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
System.out.println(Thread.currentThread().getName() + " take leadership!");
// takeLeadership() method should only return when leadership is being relinquished.
Thread.sleep(5000L);
System.out.println(Thread.currentThread().getName() + " relinquish leadership!");
}
@Override
public void stateChanged(CuratorFramework client, ConnectionState state) {
}
};
new Thread(() -> {
registerListener(listener);
}).start();
new Thread(() -> {
registerListener(listener);
}).start();
new Thread(() -> {
registerListener(listener);
}).start();
Thread.sleep(Integer.MAX_VALUE);
}
private static void registerListener(LeaderSelectorListener listener) {
// 1.Connect to zk
CuratorFramework client = CuratorFrameworkFactory.newClient(
ZK_ADDRESS,
new RetryNTimes(10, 5000)
);
client.start();
// 2.Ensure path
try {
new EnsurePath(ZK_PATH).ensure(client.getZookeeperClient());
} catch (Exception e) {
e.printStackTrace();
}
// 3.Register listener
LeaderSelector selector = new LeaderSelector(client, ZK_PATH, listener);
selector.autoRequeue();
selector.start();
}
}
☆
往期精彩
☆
關注我每天進步一點點
你點的在看,我都當成了喜歡