Zookeeper 基本概念和基礎操作
Zookeeper概述
什麼是zookeeper
- ZooKeeper
- 一個主從架構的分佈式框架、開源的對其他的分佈式框架的
提供協調服務
(service)
- 一個主從架構的分佈式框架、開源的對其他的分佈式框架的
- Zookeeper 作爲一個分佈式的服務框架
- 它提供類似於linux文件系統(有目錄節點樹)的簡版文件系統來存儲數據(所有Zookeeper中的文件都一樣)
- Zookeeper 維護和監控存儲的數據的狀態變化,通過監控這些數據狀態的變化,從而達到基於數據的集羣管理
- 主要用來解決分佈式集羣中應用系統的一致性問題
爲什麼要使用zookeeper
-
ZooKeeper簡單易用,能夠很好的解決分佈式框架在運行中,出現的各種協調問題。
- 比如集羣master主備切換、節點的上下線感知、統一命名服務、狀態同步服務、集羣管理、分佈式應用配置管理等等(hadoop+zookeeper實現HA)
Zookeeper基本概念
Zookeeper數據結構
Zookeeper主要由以下三個部分實現:
- 簡版文件系統(
Znode
)- 基於類似於文件系統的目錄節點樹方式的數據存儲
- 原語
- 可簡單理解成ZooKeeper的基本的命令
- 通知機制(
Watcher
-監聽器)
Zookeeper數據節點Znode
ZNode分爲四類
持久節點 | 臨時節點 | |
---|---|---|
非有序節點 | create | create -e |
有序節點 | create -s | create -s -e |
持久節點
-
創建節點/zk_test,並設置數據my_data
create /zk_test my_data
-
持久節點,只有顯示的調用命令,才能刪除永久節點
delete /zk_test
臨時節點
-
client1上創建臨時節點
create -e /tmp tmpdata
-
client2上查看client1創建的臨時節點
ls /
-
client1斷開連接
close
-
client2上觀察現象,發現臨時節點被自動刪除
ls /
有序節點
- 創建有序節點的意義:
- 防止多個不同的客戶端在同一目錄下,創建
同名
ZNode,由於重名,導致創建失敗
- 防止多個不同的客戶端在同一目錄下,創建
- 有序節點會在節點被創建時,Zookeeper會自動在其節點後追加一個整形數字
- 這個整數是一個由
父節點
維護的自增數字
- 提供了創建
唯一名字
的ZNode的方式
- 這個整數是一個由
如何創建有序節點
- 命令行使用-s選項
Curator編程,可添加一個特殊的屬性:CreateMode.EPHEMERAL
Zookeeper基本操作
zkCli命令行
-
啓動ZooKeeper集羣;在ZooKeeper集羣中的每個節點執行此命令
${ZK_HOME}/bin/zkServer.sh start
-
停止ZooKeeper集羣(每個節點執行以下命令)
${ZK_HOME}/bin/zkServer.sh stop
-
查看集羣狀態(每個節點執行此命令)
${ZK_HOME}/bin/zkServer.sh status
-
使用ZooKeeper自帶的腳本,連接ZooKeeper的服務器
//nodexx爲集羣幾點名 2181位通信端口,即配置文件${ZK_HOME}/conf/zoo/.cfg的ClientPort zkCli.sh -server node01:2181,node02:2181,node03:2181
客戶端會隨機
連接server後指定的服務器中的一個,並不會順序嘗試
zkCli常用命令
-
查看ZooKeeper根目錄/下的文件列表
ls /
-
創建節點,並指定數據
create /kkb kkb
-
修改節點的數據
set /kkb kkb01
-
刪除節點
delete /kkb
其他常用命令
API
創建節點API對比
-原生API
String result = zk.create("/test", "testdata".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
- Curator編程
String zNodeData = "testData";
client.create().
creatingParentsIfNeeded(). //如果父目錄不存在,則創建
withMode(CreateMode.PERSISTENT). //創建永久節點
forPath("/testPath/childPath", zNodeData.getBytes());//指定路徑及節點數據
原生API
不友好,不具體介紹
curator編程
Curator對ZooKeeper的api做了封裝,提供簡單易用的API
package cruator;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import java.util.List;
public class ZKCrud {
private static final String ZK_ADDRESS = "node01:2181,node02:2181,node03;2181";
private static final String ZK_PATH = "testPath";
static CuratorFramework client = null;
//初始化,建立連接
public static void init(){
//10次 每次間隔3000毫秒
RetryNTimes retryPolicy = new RetryNTimes(10, 3000);
client = CuratorFrameworkFactory.newClient(ZK_ADDRESS, retryPolicy);
client.start();
System.out.println("zk client started successfully");
}
public static void clean(){
System.out.println("close client");
client.close();
}
//創建永久節點
public static void createPersitsentZNode() throws Exception {
String zNodeData = "testData";
String s = client.create()
.creatingParentContainersIfNeeded() //父節點不存在就創建父節點
.withMode(CreateMode.PERSISTENT) //創建永久節點
.forPath("/testPath/childPath/", zNodeData.getBytes());//指定路徑及節點數據
print(s);
}
//創建臨時節點
public static void createEphemeralZnode() throws Exception {
String zNodeData = "testData";
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath("/testEphemeraPath/childPath",zNodeData.getBytes());
}
//查詢ZNode數據
public static void getZnodeData() throws Exception {
//查新列表
print("ls","/");
List<String> list = client.getChildren().forPath("/");
for (String s :
list) {
System.out.println(s);
}
print(client.getChildren().forPath("/"));
//查詢數據
print("get",ZK_PATH);
if (client.checkExists().forPath(ZK_PATH) != null){
print(client.getData().forPath(ZK_PATH));
}else {
print("節點不存在");
}
}
//修改節點數據
public static void setZNode() throws Exception {
// 修改節點數據
String data2 = "hello world";
print("set", ZK_PATH, data2);
if (client.checkExists().forPath(ZK_PATH) != null){
print(client.setData().forPath(ZK_PATH));
print("get", ZK_PATH);
print(client.getData().forPath(ZK_PATH));
}else {
print("節點不存在");
}
}
//刪除節點
public static void deleteZNode() throws Exception {
// 刪除節點
print("delete", ZK_PATH);
client.delete().forPath(ZK_PATH);
print("ls", "/");
print(client.getChildren().forPath("/"));
}
public static void main(String[] args) throws Exception {
init();
//createEphemeralZnode();
getZnodeData();
//deleteZNode();
clean();
}
private static void print(String... cmds) {
StringBuilder text = new StringBuilder("$ ");
for (String cmd : cmds) {
text.append(cmd).append(" ");
}
System.out.println(text.toString());
}
private static void print(Object result) {
System.out.println(
result instanceof byte[]
? new String((byte[]) result)
: result);
}
}
Curator監聽器循環有效期
Watcher
引言:會話
會話的概念
-
客戶端要對ZooKeeper集羣進行讀寫操作,得先與某一ZooKeeper服務器建立TCP長連接;此TCP長連接稱爲建立一個會話
Session
。 -
每個會話有超時時間:
SessionTimeout
- 當客戶端與集羣建立會話後,如果超過SessionTimeout時間,兩者間沒有通信,會話超時
會話的特點
- 客戶端打開
同一個
Session中的請求以FIFO
(先進先出)的順序執行;- 如客戶端client01與集羣建立會話後,先發出一個create請求,再發出一個get請求;
- 那麼在執行時,會先執行create,再執行get
- 若打開
兩個Session
,無法保證Session間,請求FIFO執行;只能保證一個session中請求的FIFO
會話的生命週期
- 未建立連接
- 正在連接
- 已連接
- 關閉連接
引言:客戶端如何獲取Zookeeper的最新數據
-
方式一輪詢:ZooKeeper以遠程服務的方式,被客戶端訪問;客戶端以輪詢的方式獲得znode數據,效率會比較低(代價比較大)
-
方式二基於通知的機制:
- 客戶端在znode上註冊一個Watcher監視器
- 當znode上數據出現變化,watcher監測到此變化,通知客戶端
什麼是Watcher
- 客戶端在服務器端,註冊事件監聽器
- watcher用於監聽
znode
數據修改,節點增刪等 - 當監聽到時間後,watcher會觸發通知客戶端
- watcher用於監聽
ZKCli設置Watcher
注意:Watcher是一個單次觸發的操作
Curator
編程中watcher可以設置循環有效
Watcher監聽節點變化
監聽的節點需要已經存在
#ls path [watch]
#node01 上執行
ls /tmp watch
#node02 上執行
create /tmp /dir01 dir01-data
#觀察node-01上變化
[zk: node-01:2181,node-02:2181,node-03:2181(CONNECTED) 87]
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/zk_test
Watcher監聽znode數據變化
#監控節點數據的變化;
#node02上
get /zk_test watch
#node03上
set /zk_test "junk01"
#觀察node2上cli的輸出,檢測到變化
節點上下線監控
- 原理:
- 節點1(client1)創建臨時節點
- 節點2(client2)在臨時節點,註冊監聽器watcher
- 當client1與zk集羣斷開連接,臨時節點會被刪除
- watcher發送消息,通知client2,臨時節點被刪除的事件
-
用到的zk特性:
Watcher+臨時節點
-
好處:
通過這種方式,檢測和被檢測系統不需要直接關聯(如client1與client2),而是通過ZK上的某個節點進行關聯,大大減少了系統耦合。
-
實現:
client1操作
# 創建臨時節點
create -e /temp tmp-data
client2操作
# 在/zk_tmp註冊監聽器
ls /temp watch
client1操作
# 模擬節點下線
close
觀察client2
Curator API設置watcher
public class CuratorWatcher {
/**
* Zookeeper info
*/
private static final String ZK_ADDRESS = "note01:2181,node02:2181,node03:2181";
private static final String ZK_PATH = "/zktest";
public static void main(String[] args) throws Exception {
// 1.Connect to zk
CuratorFramework client = CuratorFrameworkFactory.newClient(
ZK_ADDRESS,
new RetryNTimes(10, 5000)
);
client.start();
System.out.println("zk client start successfully!");
//path cache
///zktest/b/a
PathChildrenCache pathCache = new PathChildrenCache(client, ZK_PATH, true);
//Listener for PathChildrenCache changes
PathChildrenCacheListener listener = new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
switch (event.getType()) {
case CHILD_ADDED: {
System.out.println("Node added: " + ZKPaths.getNodeFromPath(event.getData().getPath()));
break;
}
case CHILD_UPDATED: {
System.out.println("Node changed: " + ZKPaths.getNodeFromPath(event.getData().getPath()));
break;
}
case CHILD_REMOVED: {
System.out.println("Node removed: " + ZKPaths.getNodeFromPath(event.getData().getPath()));
break;
}
default:
break;
}
}
};
//添加監聽器
pathCache.getListenable().addListener(listener);
pathCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
System.out.println("Register zk pathCache successfully!");
Thread.sleep(60000);
pathCache.close();
//關閉zk連接
client.close();
}
}
Zookeeper應用場景
- NameNode使用ZooKeeper實現高可用.
- Yarn ResourceManager使用ZooKeeper實現高可用.
- 利用ZooKeeper對HBase集羣做高可用配置
- kafka使用ZooKeeper
- 保存消息消費信息比如offset.
- 用於檢測崩潰
- 主題topic發現
- 保持主題的生產和消費狀態