Zookeeper的理解
Zookeeper是開源的高性能的分佈式應用協調系統,
一個高性能的分佈式數據一致性解決方案
為什麼使用Zookeeper
- 集群,可靠,解決了單機不可靠的問題
- 為了保持可靠性,當信息還沒有同步完成時,不對外提供服務,提供的數據是最新且準確的
- 同步的時間比較短,分佈式協調好,好像一個“單機”一樣
Zookeeper的特點
- 順序一致性:發送消息的的順序一致
- 原子性:集群內同步消息時的原子性
- 單一視圖:在一個集群中無論連接哪一個,看到的都是一致的
- 可靠性:直到被改變都保持不變
- 及時性:一定時間內保證客戶端能從服務器拿到最新狀態
當client發送一個請求時,由從節點發給leader處理,然後同步給其他節點
CAP
- 一致性 C
- 可用性 A
- 分區容錯性:可理解為網絡錯誤,一定會發生 P
Zookeeper和CAP的關係
CP:一致性+分區容錯性
能得到一致的數據結果,同時系統對網絡具備容錯性
但是它不能保證每次服務請求的可用性,網絡不好時可能會丟棄一些請求
作用
- 分佈式服務註冊與訂閱
- 統一配置文件:當一個配置修改後可以通過Watcher機制進行通知
- 生成分佈式唯一ID:順序節點
- Master節點選舉:創捷有編號的節點,且不能重複
- 分佈式鎖
目的:數據的最終一致性
訪問資源獲得鎖,判斷鎖是否已經被佔用(是否已經有了一個事先約定好的節點),如果已被佔用,等待釋放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將自動進行對客戶端的通知,而不是需要客戶端對服務端進行輪詢確認。
- 使用場景:統一資源配置
- Watcher的監聽類型如下:
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); } }