資安

Zookeeper入門

Zookeeper的理解

Zookeeper是開源的高性能的分佈式應用協調系統,
一個高性能的分佈式數據一致性解決方案

為什麼使用Zookeeper

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

Zookeeper的特點

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

image

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

CAP

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

Zookeeper和CAP的關係

CP:一致性+分區容錯性
能得到一致的數據結果,同時系統對網絡具備容錯性
但是它不能保證每次服務請求的可用性,網絡不好時可能會丟棄一些請求

作用

  • 分佈式服務註冊與訂閱
  • 統一配置文件:當一個配置修改後可以通過Watcher機制進行通知image
  • 生成分佈式唯一ID:順序節點image
  • Master節點選舉:創捷有編號的節點,且不能重複image
  • 分佈式鎖

目的:數據的最終一致性

訪問資源獲得鎖,判斷鎖是否已經被佔用(是否已經有了一個事先約定好的節點),如果已被佔用,等待釋放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的文件系統

image

  • 每一個節點都是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將自動進行對客戶端的通知,而不是需要客戶端對服務端進行輪詢確認。 image
  • 使用場景:統一資源配置
  • Watcher的監聽類型如下:image

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);
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *