Zookeeper的了解
Zookeeper是開源的高性能的分布式應用協調系統,
一個高性能的分布式資料一緻性解決方案
為什麼使用Zookeeper
- 叢集,可靠,解決了單機不可靠的問題
- 為了保持可靠性,當資訊還沒有同步完成時,不對外提供服務,提供的資料是最新且準确的
- 同步的時間比較短,分布式協調好,好像一個“單機”一樣
Zookeeper的特點
- 順序一緻性:發送消息的的順序一緻
- 原子性:叢集内同步消息時的原子性
- 單一視圖:在一個叢集中無論連接配接哪一個,看到的都是一緻的
- 可靠性:直到被改變都保持不變
- 及時性:一定時間内保證用戶端能從伺服器拿到最新狀态
當client發送一個請求時,由從節點發給leader處理,然後同步給其他節點
CAP
- 一緻性 C
- 可用性 A
- 分區容錯性:可了解為網絡錯誤,一定會發生 P
Zookeeper和CAP的關系
CP:一緻性+分區容錯性
能得到一緻的資料結果,同時系統對網絡具備容錯性
但是它不能保證每次服務請求的可用性,網絡不好時可能會丢棄一些請求
作用
- 分布式服務注冊與訂閱
- 統一配置檔案:當一個配置修改後可以通過Watcher機制進行通知
Zookeeper入門 - 生成分布式唯一ID:順序節點
Zookeeper入門 - Master節點選舉:創捷有編号的節點,且不能重複
Zookeeper入門 - 分布式鎖
目的:資料的最終一緻性
通路資源獲得鎖,判斷鎖是否已經被占用(是否已經有了一個事先約定好的節點),如果已被占用,等待釋放await(CountDownLatch)、如果沒被占用,建立zk節點,節點使用同一個名字,要是非持久性的鎖、目前線程擁有該鎖、業務處理完畢,釋放鎖。
Linux安裝
- 安裝一個最新的安裝包:wget https://downloads.apache.org/zookeeper/zookeeper-3.7.0/apache-zookeeper-3.7.0-bin.tar.gz
解壓:tar zxvf apache-zookeeper-3.7.0-bin.tar.gz
注:不進行配置初次運作會報錯
- 配置:cd apache-z ookeeper-3.7.0-bin cp conf/zoo_sample.cfg conf/zoo.cfg
vi conf/zoo.cfg 把内容修改為:
tickTime=2000 預設
dataDir=/var/lib/zookeeper
clientPort=2181 預設
- 啟動:./bin/zkServer.sh start
- 停止:./bin/zkServer.sh stop
注:啟動和停止都要在Zookeeper的bin目錄下
windows 下安裝
-
下載下傳 zookeeper
網址
https://zookeeper.apache.org/releases.html 下載下傳 最新版本 -
解壓 zookeeper
解壓運作 zkServer.cmd ,初次運作會報錯,沒有 zoo.cfg 配置檔案
-
修改 zoo.cfg 配置檔案
将 conf 下的 zoo_sample.cfg 複制一份改名為 zoo.cfg 即可。
注意幾個重要位置:
dataDir=./ 臨時資料存儲的目錄(可寫相對路徑) clientPort=2181 zookeeper 的端口号2181
修改完成後再次啟動 zookeeper,運作 zkServer.cmd
節點znode
是一個樹結構:類似linux的檔案系統
- 每一個節點都是znode,裡面可以包含資料,也可以有子節點
- 節點可以分為永久節點和臨時節點(session失效,也就是用戶端斷開後,臨時節點消失)
-
每個znode都有版本号,每當資料變化,版本号會累加(樂觀鎖)
使用中删除或者修改節點,版本号不比對報錯
- 節點存儲的資料不宜過大 ,會降低性能。比如可以存redis的key或者檔案的路徑
- 節點可以設定權限,來限制使用者的通路
- 讀和寫都是原子操作,每次都是對資料的完整讀取和完整寫入
節點類型:建立時就已經确定,不可更改類型
- 持久節點 PERSISTENT
- 臨時節點:PERSISTENT_SEQUENTIAL 維護服務發現
- 順序節點:EPEMERAL 持久和臨時節點都可以時順序或者非順序的(生成唯一ID)
節點版本屬性
- dataVersion : 當資料更改時+1
- cversion:當子節點改變+1
- aclVersion:當權限改變+1
常用指令linux下
- 啟動: ./bin/zkServer/.sh start
- 連接配接Server: ./bin/zkCli.sh -server 127.0.0.1 2181
- 檢視子節點:ls path
- 檢視節點狀态: stat path
- 檢視節點的資料 get path
-
建立修改删除節點:
create [-s] [-e] path [data] 不支援建立多級目錄 -s 順序 -e 臨時
set [-s] [-v version] path data -v version 樂觀鎖(多線程操作時)
delete [-v version] path 有子節點不允許删除
Watcher機制
- 觸發器。監督者:當用戶端注冊以後,如果觸發了Watcher機制,zookeeper将自動進行對用戶端的通知,而不是需要用戶端對服務端進行輪詢确認。
Zookeeper入門 - 使用場景:統一資源配置
- Watcher的監聽類型如下:
Zookeeper入門
ACL權限控制
- access control list權限控制,類似shiro
- 它使用權限位來允許/禁止對節list點及其所作用域的各種操作
- ACL僅與特定的znode有關,與子節點無關
ACL權限控制格式:
[scheme采用的權限機制:id使用者:permissions權限組合字元串]
權限機制:world、auth(明文)、digest(加密)、ip(常用)、super(最高權限)
權限字元串crdwa
Create、Read、Delete、Write、Admin(最高)
使用場景:
- 區分開發,測試,運維環境,防止誤操作
- 可以針對不同的IP而産生具體的配置,更安全
Demo
原生Java的Api的缺點
- 不支援連接配接逾時後的自動重連
- Watcher注冊一次後會失效,觸發後
- 不支援遞歸建立節點
使用Apache Curator工具類
- 解決Watcher注冊一次後失效的問題
- API更加簡單易用
1.引入依賴
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
2.測試demo
/**
* 描述: 用Curator來操作ZK
*/
public class CuratorTests {
public static void main(String[] args) throws Exception {
// 配置zookeeper位址
String connectString = "127.0.0.1:2181";
// 節點路徑
String path = "test/curator";
// 連接配接重試政策
RetryPolicy retry = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retry);
// 啟動 服務
client.start();
// 設定watcher 監聽機制
client.getCuratorListenable().addListener((CuratorFramework c, CuratorEvent event) -> {
switch (event.getType()) {
case WATCHED:
WatchedEvent watchedEvent = event.getWatchedEvent();
if (watchedEvent.getType() == EventType.NodeDataChanged) {
System.out.println(new String(c.getData().forPath(path)));
}
}
});
String data = "test";
String data2 = "test2";
// 建立一個節點
client.create().withMode(CreateMode.PERSISTENT).forPath(path, data.getBytes());
// 擷取節點資料
byte[] bytes = client.getData().watched().forPath(path);
System.out.println(new String(bytes));
// 修改一個節點
client.setData().forPath(path, data2.getBytes());
// 删除一個節點
client.delete().forPath(path);
Thread.sleep(2000);
}
}