Zookeeper簡介
基本概念
Zookeeper是一個開源的分散式協調服務。其設計目標是將那些複雜的容易出錯的分散式一致性服務封裝起來,以簡單的介面提供給使用者使用。它是一個典型的分散式資料一致性的解決方案,分散式應用程式可以基於它實現如:釋出/訂閱、負載均衡、叢集管理、分散式鎖、分散式佇列等功能。
名詞概念
- 叢集角色
Zookeeper叢集中有三種角色:Leader、Follower、Observer。Leader提供讀和寫服務。Follower和Observer能提供讀服務。Observer和Follower的區別就在於Ovserver不參與Leader選舉,不參與寫操作的過半成功策略。
因此Observer可以在不影響寫效能的情況下,提升叢集的效能。如果沒有Observer的話,一個Leader和N個Follower,如果Follower特別多的話,雖然讀效能提高了,但是寫效能和選舉的效能會受影響。
- 會話(session)
客戶端會話,一個客戶端連線是指客戶端和服務端之間的一個TCP長連線。客戶端啟動的時候,首先會和伺服器建立一個TCP長連線,從第一次建立連線開始,會話(session)的生命週期就開始了。通過這個連線,客戶端能夠通過心跳檢測與伺服器保持有效的會話,也能夠向Zookeeper伺服器傳送請求,同時還能接受伺服器的Watch事件通知。
- 資料節點(Znode)
Zookeeper資料模型中的一個單元,我們稱之為資料節點。Zookeeper將所有資料儲存在記憶體中,資料模型是一棵樹,由斜槓進行分割的路徑,就是一個Znode。如/app。每個Znode都能儲存自己的資料內容,還會儲存屬性資訊。
4. 版本
每個Znode都有一個叫作Stat的資料結構,Stat裡記錄了Znode的三個資料版本,分別是version(當前Znode的版本)、cversion(當前Znode子節點的版本)、aversion(當前Znode的ACL版本)
5. Watcher(事件監聽器)
Watcher是Zookeeper中一個很重要的特性,Zookeeper允許使用者在指定節點上註冊一些Watcher,並且在一些特定事件觸發的時候,Zookeeper服務端會將事件通知到感興趣的客戶端,該機制是Zookeeper實現分散式協調服務的重要特性。
6. ACL
Zookeeper採用ACL策略來進行許可權控制,它定義了五種許可權:
- CREATE:建立子節點的許可權
- READ:獲取節點資料和子節點列表的許可權
- WRITE:更新節點資料的許可權
- DELETE:刪除子節點的許可權
- ADMIN:設定子節點ACL的許可權。
注意:CREATE和DELETE這兩種許可權是針對子節點的許可權控制
Zookeeper命令列
- 客戶端連線
./zkCli.sh #連線本地的zookeeper伺服器
./zkCli.sh -server ip:port # 連線遠端的伺服器
連線成功之後,系統會輸出Zookeeper的相關環境及配置資訊等資訊
- 建立節點
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
#建立順序節點
create -s /cc
#建立臨時節點,客戶端會話結束後,節點會自動刪除
create -e /temp
#建立帶內容的節點
create /hi nihao
- 讀取節點
#ls命令會列出path節點下所有的子節點
ls path
#get命令會查詢到path節點的資料內容,加上-s可以查詢更詳細的資訊
get [-s] path
- 更新節點
set [-s] [-v version] path data
#修改/abc節點的內容為hello,加上-s會返回更詳細的資訊
set /abc hello
- 刪除節點
#刪除,如果帶上了版本引數,那麼刪除的時候就會校驗版本是否正確,正確才進行刪除
delete [-v version] path
Zookeeper 客戶端API
官方原生API
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
</dependency>
很少直接使用了,介面介紹省略
ZkClient API
ZkClient是github上一個開源的zookeeper客戶端,在原生API的基礎上進行了包裝,更加易用。同時還實現瞭如Session超時重連、Watcher反覆註冊等功能。
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.2</version>
</dependency>
直接上程式碼:
public class ClientDemo {
private static final String connectStr="192.168.56.115:2181";
public static void main(String[] args) throws InterruptedException {
// create();
// delete();
// get();
getData();
}
public static void create() throws InterruptedException {
ZkClient zkClient=new ZkClient(connectStr);
//第二個引數,true代表遞迴建立節點,沒有父節點先建立父節點
zkClient.createPersistent("/test/node01",true);
//持久有序的node
zkClient.createPersistentSequential("/test/node02","data");
//臨時node
zkClient.createEphemeral("/ephemeral");
}
public static void delete(){
ZkClient zkClient=new ZkClient(connectStr);
//普通刪除
zkClient.delete("/test");
//遍歷刪除,先刪除該節點的所有子節點,再刪除它本身
zkClient.deleteRecursive("/test");
}
public static void get() throws InterruptedException {
ZkClient zkClient=new ZkClient(connectStr);
List<String> children = zkClient.getChildren("/test");
System.out.println("子節點:"+children);
zkClient.subscribeChildChanges("/watch", new IZkChildListener() {
@Override
public void handleChildChange(String s, List<String> list) throws Exception {
System.out.println(s + " child changed,list:" + list);
}
});
zkClient.createPersistent("/watch");
Thread.sleep(1000);
zkClient.createPersistent("/watch/test");
Thread.sleep(1000);
zkClient.delete("/watch/test");
Thread.sleep(1000);
zkClient.delete("/watch");
}
//獲取資料
public static void getData() throws InterruptedException {
ZkClient zkClient=new ZkClient(connectStr);
String path="/abc";
boolean exists = zkClient.exists(path);
System.out.println("節點是否存在:"+exists);
if(!exists){
zkClient.createEphemeral(path,"123");
}
//資料改變監聽
zkClient.subscribeDataChanges(path, new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
System.out.println("節點:"+s+" 資料被改變:"+o);
}
@Override
public void handleDataDeleted(String s) throws Exception {
System.out.println("節點被刪除:"+s);
}
});
//讀取資料
Object o = zkClient.readData(path);
System.out.println("讀取節點的資料:"+o);
Thread.sleep(3000);
//更新資料
Stat stat = zkClient.writeData(path, "456");
Thread.sleep(3000);
//刪除資料
zkClient.delete(path);
Thread.sleep(3000);
}
}
Curator API
Curator是Netflix公司開源的客戶端框架。它實現了連線重連、Watcher反覆註冊、重試策略和NodeExistsException異常解決等等。
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
直接上程式碼:
public class CuratorDemo {
private static final String connectStr="192.168.56.115:2181";
private static String path ="/abc";
public static void main(String[] args) throws Exception {
// connect();
// create();
// delete();
// get();
update();
}
public static void connect(){
//連線方式1
/*
構造器含有三個引數 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⽤
這個時間,預設的最⼤時間是Integer.MAX_VALUE毫秒。
*/
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectStr, 5000, 3000, retryPolicy);
client.start();
//連線方式2
CuratorFramework Client = CuratorFrameworkFactory.builder()
.connectString("server1:2181,server2:2181,server3:2181")
.sessionTimeoutMs(50000)
.connectionTimeoutMs(30000)
.retryPolicy(retryPolicy)
.build();
client.start();
}
public static void create() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectStr, 5000, 30000, retryPolicy);
client.start();
Thread.sleep(2000);
//建立一個內容為空的節點,curator預設是建立持久節點
// client.create().forPath(path);
//建立一個有內容的節點
client.create().forPath(path,"123".getBytes());
//呼叫creatingParentsIfNeeded 介面,Curator 就能夠自動地遞迴建立所有需要的父節點
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/tt");
}
public static void delete() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectStr, 5000, 30000, retryPolicy);
client.start();
//刪除節點
client.delete().forPath(path);
//刪除節點,並遞迴刪除子節點
client.delete().deletingChildrenIfNeeded().forPath(path);
//指定版本刪除ls
client.delete().withVersion(1).forPath(path);
//強制刪除。只要客戶端會話有效,那麼Curator會在後臺持續進⾏刪除操作,直到節點刪除成功。⽐如遇到⼀些⽹
//絡異常的情況,此guaranteed的強制刪除就會很有效果
client.delete().guaranteed().forPath(path);
}
public static void get() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectStr, 5000, 30000, retryPolicy);
client.start();
//普通查詢
byte[] bytes = client.getData().forPath(path);
System.out.println("節點內容:"+new String(bytes));
// 包含狀態查詢
Stat stat = new Stat();
byte[] bytes1 = client.getData().storingStatIn(stat).forPath(path);
System.out.println(stat.getVersion());
}
public static void update() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectStr, 5000, 30000, retryPolicy);
client.start();
// 普通更新
client.setData().forPath(path,"新內容".getBytes());
// 指定版本更新 當攜帶資料版本不⼀致時,無法完成更新操作
// 異常資訊:Exception in thread "main" org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for /abc
client.setData().withVersion(1).forPath(path,"abcd".getBytes());
}