天天看點

Zookeeper入門

Zookeeper的了解

Zookeeper是開源的高性能的分布式應用協調系統,

一個高性能的分布式資料一緻性解決方案

為什麼使用Zookeeper

  • 叢集,可靠,解決了單機不可靠的問題
  • 為了保持可靠性,當資訊還沒有同步完成時,不對外提供服務,提供的資料是最新且準确的
  • 同步的時間比較短,分布式協調好,好像一個“單機”一樣

Zookeeper的特點

  • 順序一緻性:發送消息的的順序一緻
  • 原子性:叢集内同步消息時的原子性
  • 單一視圖:在一個叢集中無論連接配接哪一個,看到的都是一緻的
  • 可靠性:直到被改變都保持不變
  • 及時性:一定時間内保證用戶端能從伺服器拿到最新狀态

當client發送一個請求時,由從節點發給leader處理,然後同步給其他節點

CAP

  • 一緻性 C
  • 可用性 A
  • 分區容錯性:可了解為網絡錯誤,一定會發生 P

Zookeeper和CAP的關系

CP:一緻性+分區容錯性

能得到一緻的資料結果,同時系統對網絡具備容錯性

但是它不能保證每次服務請求的可用性,網絡不好時可能會丢棄一些請求

作用

  • 分布式服務注冊與訂閱
  • 統一配置檔案:當一個配置修改後可以通過Watcher機制進行通知
    Zookeeper入門
  • 生成分布式唯一ID:順序節點
    Zookeeper入門
  • Master節點選舉:創捷有編号的節點,且不能重複
    Zookeeper入門
  • 分布式鎖

目的:資料的最終一緻性

通路資源獲得鎖,判斷鎖是否已經被占用(是否已經有了一個事先約定好的節點),如果已被占用,等待釋放await(CountDownLatch)、如果沒被占用,建立zk節點,節點使用同一個名字,要是非持久性的鎖、目前線程擁有該鎖、業務處理完畢,釋放鎖。

Linux安裝

解壓: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的檔案系統

Zookeeper入門
  • 每一個節點都是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);
    }
}