Zookeeper 是一個分佈式協調服務的開源框架。 主要用來解決分佈式集羣中
應用系統的一致性問題,例如怎樣避免同時操做同一數據形成髒讀的問題。
ZooKeeper 本質上是一個分佈式的小文件存儲系統。 提供基於相似於文件系
統的目錄樹方式的數據存儲,而且能夠對樹中的節點進行有效管理。從而用來維
護和監控你存儲的數據的狀態變化。經過監控這些數據狀態的變化,從而能夠達
到基於數據的集羣管理。 諸如: 統一命名服務(dubbo)、分佈式配置管理(solr的配置集中管理)、分佈式消息隊列(sub/pub)、分佈式鎖、分佈式協調等功能。java
Leader:
Zookeeper 集羣工做的核心
事務請求(寫操做) 的惟一調度和處理者,保證集羣事務處理的順序性;
集羣內部各個服務器的調度者。
對於 create, setData, delete 等有寫操做的請求,則須要統一轉發給leader 處理, leader 須要決定編號、執行操做,這個過程稱爲一個事務。
Follower:
處理客戶端非事務(讀操做) 請求,node
轉發事務請求給 Leader;linux
參與集羣 Leader 選舉投票 2n-1臺能夠作集羣投票。算法
此外,針對訪問量比較大的 zookeeper 集羣, 還可新增觀察者角色。shell
Observer:
觀察者角色,觀察 Zookeeper 集羣的最新狀態變化並將這些狀態同步過
來,其對於非事務請求能夠進行獨立處理,對於事務請求,則會轉發給 Leader
服務器進行處理。
不會參與任何形式的投票只提供非事務服務,一般用於在不影響集羣事務
處理能力的前提下提高集羣的非事務處理能力。數據庫
扯淡:說白了就是增長併發的讀請求apache
1.全局數據一致:每一個 server 保存一份相同的數據副本, client 不管連
接到哪一個 server,展現的數據都是一致的,這是最重要的特徵;
2. 可靠性:若是消息被其中一臺服務器接受,那麼將被全部的服務器接受。
3. 順序性:包括全局有序和偏序兩種:全局有序是指若是在一臺服務器上
消息 a 在消息 b 前發佈,則在全部 Server 上消息 a 都將在消息 b 前被
發佈;偏序是指若是一個消息 b 在消息 a 後被同一個發送者發佈, a 必
將排在 b 前面。
4. 數據更新原子性:一次數據更新要麼成功(半數以上節點成功),要麼失
敗,不存在中間狀態;
5. 實時性: Zookeeper 保證客戶端將在一個時間間隔範圍內得到服務器的
更新信息,或者服務器失效的信息。vim
Zookeeper 集羣搭建指的是 ZooKeeper 分佈式模式安裝。 一般由 2n+1
臺 servers 組成。 這是由於爲了保證 Leader 選舉(基於 Paxos 算法的實
現) 能過獲得多數的支持,因此 ZooKeeper 集羣的數量通常爲奇數。
Zookeeper 運行須要 java 環境, 因此須要提早安裝 jdk。 對於安裝
leader+follower 模式的集羣, 大體過程以下:
l 配置主機名稱到 IP 地址映射配置
l 修改 ZooKeeper 配置文件
l 遠程複製分發安裝文件
l 設置 myid
l 啓動 ZooKeeper 集羣
若是要想使用 Observer 模式,可在對應節點的配置文件添加以下配置:
peerType=observer
其次,必須在配置文件指定哪些節點被指定爲 Observer,如:
server.1:localhost:2181:3181:observer服務器
服務器IPsession |
主機名 |
myid的值 |
192.168.221.100 |
node01 |
1 |
192.168.221.110 |
node02 |
2 |
192.168.221.120 |
node03 |
3 |
http://archive.apache.org/dist/zookeeper/
咱們在這個網址下載咱們使用的zk版本爲3.4.9
下載完成以後,上傳到咱們的linux的/export/softwares路徑下準備進行安裝
解壓zookeeper的壓縮包到/export/servers路徑下去,而後準備進行安裝
cd /export/softwares
tar -zxvf zookeeper-3.4.9.tar.gz -C ../servers/
第一臺機器修改配置文件
cd /export/servers/zookeeper-3.4.9/conf/
cp zoo_sample.cfg zoo.cfg
mkdir -p /export/servers/zookeeper-3.4.9/zkdatas/
vim zoo.cfg
dataDir=/export/servers/zookeeper-3.4.9/zkdatas
autopurge.snapRetainCount=3
autopurge.purgeInterval=1
server.1=node01:2888:3888
server.2=node02:2888:3888
server.3=node03:2888:3888
在第一臺機器的
/export/servers/zookeeper-3.4.9/zkdatas/這個路徑下建立一個文件,文件名爲myid ,文件內容爲1
echo 1 > /export/servers/zookeeper-3.4.9/zkdatas/myid
安裝包分發到其餘機器
第一臺機器上面執行如下兩個命令
scp -r /export/servers/zookeeper-3.4.9/ node02:/export/servers/
scp -r /export/servers/zookeeper-3.4.9/ node03:/export/servers/
第二臺機器上修改myid的值爲2
echo 2 > /export/servers/zookeeper-3.4.9/zkdatas/myid
三臺機器上修改myid的值爲3
echo 3 > /export/servers/zookeeper-3.4.9/zkdatas/myid
三臺機器啓動zookeeper服務
這個命令三臺機器都要執行
/export/servers/zookeeper-3.4.9/bin/zkServer.sh start
查看啓動狀態
/export/servers/zookeeper-3.4.9/bin/zkServer.sh status
運行 zkCli.sh –server ip 進入命令行工具。
輸入 help,輸出 zk shell 提示:
建立節點
create [-s] [-e] path data acl
其中,-s 或-e 分別指定節點特性,順序或臨時節點,若不指定,則表示持 久節點;acl 用來進行權限控制。
建立順序節點:
建立永久節點:
讀取節點
與讀取相關的命令有 ls 命令和 get 命令,ls 命令能夠列出 Zookeeper 指
定節點下的全部子節點,只能查看指定節點下的第一級的全部子節點;get 命令
能夠獲取 Zookeeper 指定節點的數據內容和屬性信息。 ls path [watch]
get path [watch] ls2 path [watch]
set path data [version]
data 就是要更新的新內容,version 表示數據版本。
如今 dataVersion 已經變爲 1 了,表示進行了更新。
刪除節點
delete path [version]
若刪除節點存在子節點,那麼沒法刪除該節點,必須先刪除子節點,再刪除父節點。
Rmr path
能夠遞歸刪除節點。
quota
setquota -n|-b val path 對節點增長限制。 n:表示子節點的最大個數 b:表示數據值的最大長度 val:子節點最大個數或數據值的最大長度 path:節點路徑
listquota path 列出指定節點的 quota
子節點個數爲 2,數據長度-1 表示沒限制
delquota [-n|-b] path 刪除 quota
其餘命令
history : 列出命令歷史
redo:該命令能夠從新執行指定命令編號的歷史命令,命令編號能夠經過
history 查看
ZooKeeper 的數據模型,在結構上和標準文件系統的很是類似,擁有一個層
次的命名空間,都是採用樹形層次結構,ZooKeeper 樹中的每一個節點被稱爲—
Znode。和文件系統的目錄樹同樣,ZooKeeper 樹中的每一個節點能夠擁有子節點。
但也有不一樣之處:
圖中的每一個節點稱爲一個 Znode。 每一個 Znode 由 3 部分組成:
① stat:此爲狀態信息, 描述該 Znode 的版本, 權限等信息
② data:與該 Znode 關聯的數據
③ children:該 Znode 下的子節點
Znode 有兩種,分別爲臨時節點和永久節點。
節點的類型在建立時即被肯定,而且不能改變。
臨時節點:該節點的生命週期依賴於建立它們的會話。一旦會話結束,臨時 節點將被自動刪除,固然能夠也能夠手動刪除。臨時節點不容許擁有子節點。
永久節點:該節點的生命週期不依賴於會話,而且只有在客戶端顯示執行刪
除操做的時候,他們才能被刪除。
Znode 還有一個序列化的特性,若是建立的時候指定的話,該 Znode 的名字
後面會自動追加一個不斷增長的序列號。序列號對於此節點的父節點來講是惟一
的,這樣便會記錄每一個子節點建立的前後順序。它的格式爲「%10d」(10 位數字,
沒有數值的數位用 0 補充,例如「0000000001」)。
這樣便會存在四種類型的 Znode 節點,分別對應:
PERSISTENT:永久節點
EPHEMERAL:臨時節點
PERSISTENT_SEQUENTIAL:永久節點、序列化
EPHEMERAL_SEQUENTIAL:臨時節點、序列化
建立永久節點:
[zk: localhost:2181(CONNECTED) 3] create /hello world
Created /hello
建立臨時節點:
[zk: localhost:2181(CONNECTED) 5] create -e /abc 123
Created /abc
建立永久序列化節點:
[zk: localhost:2181(CONNECTED) 6] create -s /zhangsan boy
Created /zhangsan0000000004
建立臨時序列化節點:
zk: localhost:2181(CONNECTED) 11] create -e -s /lisi boy
Created /lisi0000000006
每一個 znode 都包含了一系列的屬性,經過命令 get,能夠得到節點的屬性。
dataVersion:數據版本號,每次對節點進行 set 操做,dataVersion 的值都
會增長 1(即便設置的是相同的數據),可有效避免了數據更新時出現的前後順
序問題。
cversion :子節點的版本號。當 znode 的子節點有變化時,cversion 的值
就會增長 1。
aclVersion :ACL 的版本號。
cZxid :Znode 建立的事務 id。
mZxid :Znode 被修改的事務 id,即每次對 znode 的修改都會更新 mZxid。
對於 zk 來講,每次的變化都會產生一個惟一的事務 id,zxid(ZooKeeper Transaction Id)。經過 zxid,能夠肯定更新操做的前後順序。例如,若是 zxid1
小於 zxid2,說明 zxid1 操做先於 zxid2 發生,zxid 對於整個 zk 都是惟一的,
即便操做的是不一樣的 znode。
ctime:節點建立時的時間戳.
mtime:節點最新一次更新發生時的時間戳.
ephemeralOwner:若是該節點爲臨時節點, ephemeralOwner 值表示與該節點
綁定的 session id. 若是不是, ephemeralOwner 值爲 0.
在 client 和 server 通訊以前,首先須要創建鏈接,該鏈接稱爲 session。連
接創建後,若是發生鏈接超時、受權失敗,或者顯式關閉鏈接,鏈接便處於 CLOSED
狀態, 此時 session 結束。
ZooKeeper 提供了分佈式數據發佈/訂閱功能,一個典型的發佈/訂閱模型系
統定義了一種一對多的訂閱關係,能讓多個訂閱者同時監聽某一個主題對象,當
這個主題對象自身狀態變化時,會通知全部訂閱者,使他們可以作出相應的處理。
ZooKeeper 中,引入了 Watcher 機制來實現這種分佈式的通知功能 。
ZooKeeper 容許客戶端向服務端註冊一個 Watcher 監聽,當服務端的一些事件觸
發了這個 Watcher,那麼就會向指定客戶端發送一個事件通知來實現分佈式的通知功能。
觸發事件種類不少,如:節點建立,節點刪除,節點改變,子節點改變等。
總的來講能夠歸納 Watcher 爲如下三個過程:客戶端向服務端註冊 Watcher、
服務端事件發生觸發 Watcher、客戶端回調 Watcher 獲得觸發事件狀況
一次性觸發
事件發生觸發監聽,一個 watcher event 就會被髮送到設置監聽的客戶端,
這種效果是一次性的,後續再次發生一樣的事件,不會再次觸發。
事件封裝
ZooKeeper 使用 WatchedEvent 對象來封裝服務端事件並傳遞。
WatchedEvent 包含了每個事件的三個基本屬性:
通知狀態(keeperState),事件類型(EventType)和節點路徑(path)
event 異步發送
watcher 的通知事件從服務端發送到客戶端是異步的。
先註冊再觸發
Zookeeper 中的 watch 機制,必須客戶端先去服務端註冊監聽,這樣事件發
送纔會觸發監聽,通知給客戶端。
同一個事件類型在不一樣的通知狀態中表明的含義有所不一樣,下表列舉了常見
的通知狀態和事件類型。
KeeperState |
EventType |
觸發條件 |
說明 |
|
None (-1) |
客戶端與服務端成功創建鏈接 |
|
SyncConnected (0) |
NodeCreated (1) |
Watcher 監聽的對應數據節點被建立 |
|
NodeDeleted (2) |
Watcher 監聽的對應數據節點被刪除 |
此時客戶端和服務器處於鏈接狀態 |
|
NodeDataChanged (3) |
Watcher 監聽的對應數據節點的數據內容 發生變動 |
|
|
NodeChildChanged (4) |
Wather 監聽的對應節點的子節點數據列表發生變動
|
|
|
Disconnected (0) |
None (-1) |
客戶端與zookeeper服務器斷開鏈接 |
此時客戶端和服務器處於斷開鏈接狀態 |
Expired (-112) |
Node (-1) |
會話超時 |
此時客戶端會話失效,一般同時也會收到SessionExpiredException 異常 |
AuthFailed (4) |
None (-1) |
一般有兩種狀況 1:使用錯誤的 schema 進行權限檢查 2:SASL 權限檢查失敗 |
一般同時也會收到AuthFailedException 異常 |
其中鏈接狀態事件(type=None, path=null)不須要客戶端註冊,客戶端只要
有須要直接處理就好了。
設置節點數據變更監聽:
經過另外一個客戶端更改節點數據:
此時設置監聽的節點收到通知:
Zookeeper 是在 Java 中客戶端主類,負責創建與 zookeeper 集羣的會話,並提供方法進行操做。org.apache.zookeeper.Watcher
Watcher 接口表示一個標準的事件處理器,其定義了事件通知相關的邏輯,
包含 KeeperState 和 EventType 兩個枚舉類,分別表明了通知狀態和事件類型,
同時定義了事件的回調方法:process(WatchedEvent event)。
process 方法是 Watcher 接口中的一個回調方法,當 ZooKeeper 向客戶端發
送一個 Watcher 事件通知時,客戶端就會對相應的 process 方法進行回調,從而
實現對事件的處理。
建立maven java工程,導入jar包
<!-- <repositories>
<repository>
<id>cloudera</id>
<url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
</repository>
</repositories> -->
<dependencies>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>com.google.collections</groupId>
<artifactId>google-collections</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- java編譯插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
/**
* 建立永久節點
* @throws Exception
*/
@Test
public void createNode() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 1);
//獲取客戶端對象
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.221.100:2181,192.168.221.110:2181,192.168.221.120:2181", 1000, 1000, retryPolicy);
//調用start開啓客戶端操做
client.start();
//經過create來進行建立節點,而且須要指定節點類型
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/hello3/world");
client.close();
}
/**
* 建立臨時節點
* @throws Exception
*/
@Test
public void createNode2() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 1);
CuratorFramework client = CuratorFrameworkFactory.newClient("node01:2181,node02:2181,node03:2181", 3000, 3000, retryPolicy);
client.start();
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/hello5/world");
Thread.sleep(5000);
client.close();
}
/**
* 節點下面添加數據與修改是相似的,一個節點下面會有一個數據,新的數據會覆蓋舊的數據
* @throws Exception
*/
@Test
public void nodeData() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 1);
CuratorFramework client = CuratorFrameworkFactory.newClient("node01:2181,node02:2181,node03:2181", 3000, 3000, retryPolicy);
client.start();
client.setData().forPath("/hello5", "hello7".getBytes());
client.close();
}
/**
* 數據查詢
*/
@Test
public void updateNode() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 1);
CuratorFramework client = CuratorFrameworkFactory.newClient("node01:2181,node02:2181,node03:2181", 3000, 3000, retryPolicy);
client.start();
byte[] forPath = client.getData().forPath("/hello5");
System.out.println(new String(forPath));
client.close();
}
/**
* zookeeper的watch機制
* @throws Exception
*/
@Test
public void watchNode() throws Exception {
RetryPolicy policy = new ExponentialBackoffRetry(3000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("node01:2181,node02:2181,node03:2181", policy);
client.start();
// ExecutorService pool = Executors.newCachedThreadPool();
//設置節點的cache
TreeCache treeCache = new TreeCache(client, "/hello5");
//設置監聽器和處理過程
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
ChildData data = event.getData();
if(data !=null){
switch (event.getType()) {
case NODE_ADDED:
System.out.println("NODE_ADDED : "+ data.getPath() +" 數據:"+ new String(data.getData()));
break;
case NODE_REMOVED:
System.out.println("NODE_REMOVED : "+ data.getPath() +" 數據:"+ new String(data.getData()));
break;
case NODE_UPDATED:
System.out.println("NODE_UPDATED : "+ data.getPath() +" 數據:"+ new String(data.getData()));
break;
default:
break;
}
}else{
System.out.println( "data is null : "+ event.getType());
}
}
});
//開始監聽
treeCache.start();
Thread.sleep(50000000);
}