Zookeeper-1 基本概念和基礎操作

Zookeeper概述

什麼是zookeeper

  • ZooKeeper
    • 一個主從架構的分佈式框架、開源的對其他的分佈式框架的提供協調服務(service)
  • Zookeeper 作爲一個分佈式的服務框架
    • 它提供類似於linux文件系統(有目錄節點樹)的簡版文件系統來存儲數據(所有Zookeeper中的文件都一樣)
    • Zookeeper 維護和監控存儲的數據的狀態變化,通過監控這些數據狀態的變化,從而達到基於數據的集羣管理
    • 主要用來解決分佈式集羣中應用系統的一致性問題
      在這裏插入圖片描述

爲什麼要使用zookeeper

  • ZooKeeper簡單易用,能夠很好的解決分佈式框架在運行中,出現的各種協調問題。

    • 比如集羣master主備切換、節點的上下線感知、統一命名服務、狀態同步服務、集羣管理、分佈式應用配置管理等等(hadoop+zookeeper實現HA)

Zookeeper基本概念

Zookeeper數據結構

Zookeeper主要由以下三個部分實現:

  • 簡版文件系統(Znode)
    • 基於類似於文件系統的目錄節點樹方式的數據存儲
  • 原語
    • 可簡單理解成ZooKeeper的基本的命令
  • 通知機制(Watcher-監聽器)

Zookeeper數據節點Znode

ZNode分爲四類

持久節點 臨時節點
非有序節點 create create -e
有序節點 create -s create -s -e

持久節點

  • 創建節點/zk_test,並設置數據my_data

    create /zk_test my_data
    
  • 持久節點,只有顯示的調用命令,才能刪除永久節點

    delete /zk_test
    

臨時節點

  • client1上創建臨時節點

    create -e /tmp tmpdata
    
  • client2上查看client1創建的臨時節點

    ls /
    
  • client1斷開連接

    close
    
  • client2上觀察現象,發現臨時節點被自動刪除

    ls /
    

有序節點

  • 創建有序節點的意義:
    • 防止多個不同的客戶端在同一目錄下,創建同名ZNode,由於重名,導致創建失敗
  • 有序節點會在節點被創建時,Zookeeper會自動在其節點後追加一個整形數字
    • 這個整數是一個由父節點維護的自增數字
    • 提供了創建唯一名字的ZNode的方式

如何創建有序節點

  • 命令行使用-s選項
    在這裏插入圖片描述
    Curator編程,可添加一個特殊的屬性:CreateMode.EPHEMERAL

Zookeeper基本操作

zkCli命令行

  • 啓動ZooKeeper集羣;在ZooKeeper集羣中的每個節點執行此命令

    ${ZK_HOME}/bin/zkServer.sh start
    
  • 停止ZooKeeper集羣(每個節點執行以下命令)

    ${ZK_HOME}/bin/zkServer.sh stop
    
  • 查看集羣狀態(每個節點執行此命令)

    ${ZK_HOME}/bin/zkServer.sh status
    
  • 使用ZooKeeper自帶的腳本,連接ZooKeeper的服務器

    //nodexx爲集羣幾點名 2181位通信端口,即配置文件${ZK_HOME}/conf/zoo/.cfg的ClientPort
    zkCli.sh -server node01:2181,node02:2181,node03:2181
    

客戶端會隨機連接server後指定的服務器中的一個,並不會順序嘗試

zkCli常用命令

  • 查看ZooKeeper根目錄/下的文件列表

    ls /
    
  • 創建節點,並指定數據

    create /kkb	kkb
    
  • 修改節點的數據

    set /kkb kkb01
    
  • 刪除節點

    delete /kkb
    

其他常用命令

在這裏插入圖片描述

API

創建節點API對比
-原生API

  String result = zk.create("/test", "testdata".getBytes(), 
  ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  • Curator編程
String zNodeData = "testData";
  client.create().
                creatingParentsIfNeeded().          //如果父目錄不存在,則創建
                withMode(CreateMode.PERSISTENT).    //創建永久節點
                forPath("/testPath/childPath", zNodeData.getBytes());//指定路徑及節點數據

原生API

不友好,不具體介紹

curator編程

Curator對ZooKeeper的api做了封裝,提供簡單易用的API
package cruator;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;

import java.util.List;

public class ZKCrud {
    private static final String ZK_ADDRESS = "node01:2181,node02:2181,node03;2181";
    private static final String ZK_PATH = "testPath";

    static CuratorFramework client = null;

    //初始化,建立連接
    public static void init(){
        //10次 每次間隔3000毫秒
        RetryNTimes retryPolicy = new RetryNTimes(10, 3000);
        client = CuratorFrameworkFactory.newClient(ZK_ADDRESS, retryPolicy);
        client.start();

        System.out.println("zk client started successfully");
    }

    public static void clean(){
        System.out.println("close client");
        client.close();
    }

    //創建永久節點
    public static void createPersitsentZNode() throws Exception {
        String zNodeData = "testData";
        String s = client.create()
                .creatingParentContainersIfNeeded()                     //父節點不存在就創建父節點
                .withMode(CreateMode.PERSISTENT)                        //創建永久節點
                .forPath("/testPath/childPath/", zNodeData.getBytes());//指定路徑及節點數據
        print(s);

    }

    //創建臨時節點
    public static void createEphemeralZnode() throws Exception {
        String zNodeData = "testData";
        client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL)
                .forPath("/testEphemeraPath/childPath",zNodeData.getBytes());
    }

    //查詢ZNode數據
    public static void getZnodeData() throws Exception {
        //查新列表
        print("ls","/");
        List<String> list = client.getChildren().forPath("/");
        for (String s :
                list) {
            System.out.println(s);
        }
        print(client.getChildren().forPath("/"));

        //查詢數據
        print("get",ZK_PATH);
        if (client.checkExists().forPath(ZK_PATH) != null){
            print(client.getData().forPath(ZK_PATH));
        }else {
            print("節點不存在");
        }
    }

    //修改節點數據
    public static void setZNode() throws Exception {
        // 修改節點數據
        String data2 = "hello world";
        print("set", ZK_PATH, data2);

        if (client.checkExists().forPath(ZK_PATH) != null){
            print(client.setData().forPath(ZK_PATH));
            print("get", ZK_PATH);
            print(client.getData().forPath(ZK_PATH));
        }else {
            print("節點不存在");
        }
    }

    //刪除節點
    public static void deleteZNode() throws Exception {
        // 刪除節點
        print("delete", ZK_PATH);
        client.delete().forPath(ZK_PATH);

        print("ls", "/");
        print(client.getChildren().forPath("/"));
    }


    public static void main(String[] args) throws Exception {
        init();
        //createEphemeralZnode();
        getZnodeData();
        //deleteZNode();
        clean();

    }

    private static void print(String... cmds) {
        StringBuilder text = new StringBuilder("$ ");
        for (String cmd : cmds) {
            text.append(cmd).append(" ");
        }
        System.out.println(text.toString());
    }

    private static void print(Object result) {
        System.out.println(
                result instanceof byte[]
                        ? new String((byte[]) result)
                        : result);
    }
}

Curator監聽器循環有效期

Watcher

引言:會話

會話的概念

  • 客戶端要對ZooKeeper集羣進行讀寫操作,得先與某一ZooKeeper服務器建立TCP長連接;此TCP長連接稱爲建立一個會話Session

  • 每個會話有超時時間:SessionTimeout

    • 當客戶端與集羣建立會話後,如果超過SessionTimeout時間,兩者間沒有通信,會話超時

會話的特點

  • 客戶端打開同一個Session中的請求以FIFO(先進先出)的順序執行;
    • 如客戶端client01與集羣建立會話後,先發出一個create請求,再發出一個get請求;
    • 那麼在執行時,會先執行create,再執行get
  • 若打開兩個Session,無法保證Session間,請求FIFO執行;只能保證一個session中請求的FIFO

會話的生命週期

在這裏插入圖片描述

  • 未建立連接
  • 正在連接
  • 已連接
  • 關閉連接

引言:客戶端如何獲取Zookeeper的最新數據

  • 方式一輪詢:ZooKeeper以遠程服務的方式,被客戶端訪問;客戶端以輪詢的方式獲得znode數據,效率會比較低(代價比較大)
    在這裏插入圖片描述

  • 方式二基於通知的機制:

    • 客戶端在znode上註冊一個Watcher監視器
    • 當znode上數據出現變化,watcher監測到此變化,通知客戶端
      在這裏插入圖片描述

什麼是Watcher

  • 客戶端在服務器端,註冊事件監聽器
    • watcher用於監聽znode數據修改,節點增刪等
    • 當監聽到時間後,watcher會觸發通知客戶端

ZKCli設置Watcher

注意:Watcher是一個單次觸發的操作
Curator編程中watcher可以設置循環有效

Watcher監聽節點變化

監聽的節點需要已經存在
#ls path [watch]
#node01 上執行 
ls /tmp watch

#node02 上執行
create /tmp /dir01 dir01-data

#觀察node-01上變化
[zk: node-01:2181,node-02:2181,node-03:2181(CONNECTED) 87] 
WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/zk_test

Watcher監聽znode數據變化

#監控節點數據的變化;
#node02上
get /zk_test watch

#node03上
set /zk_test "junk01"
#觀察node2上cli的輸出,檢測到變化

節點上下線監控

  • 原理:
  1. 節點1(client1)創建臨時節點
  2. 節點2(client2)在臨時節點,註冊監聽器watcher
  3. 當client1與zk集羣斷開連接,臨時節點會被刪除
  4. watcher發送消息,通知client2,臨時節點被刪除的事件
  • 用到的zk特性:

    ​ Watcher+臨時節點

  • 好處:

    ​ 通過這種方式,檢測和被檢測系統不需要直接關聯(如client1與client2),而是通過ZK上的某個節點進行關聯,大大減少了系統耦合

  • 實現:
    client1操作

	# 創建臨時節點
	create -e /temp tmp-data

client2操作

	# 在/zk_tmp註冊監聽器
	ls /temp watch

client1操作

	# 模擬節點下線
	close

觀察client2
在這裏插入圖片描述

Curator API設置watcher

public class CuratorWatcher {
    /**
     * Zookeeper info
     */
    private static final String ZK_ADDRESS = "note01:2181,node02:2181,node03:2181";
    private static final String ZK_PATH = "/zktest";

    public static void main(String[] args) throws Exception {
        // 1.Connect to zk
        CuratorFramework client = CuratorFrameworkFactory.newClient(
                ZK_ADDRESS,
                new RetryNTimes(10, 5000)
        );
        client.start();
        System.out.println("zk client start successfully!");

        //path cache
        ///zktest/b/a
        PathChildrenCache pathCache = new PathChildrenCache(client, ZK_PATH, true);

        //Listener for PathChildrenCache changes
        PathChildrenCacheListener listener = new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                switch (event.getType()) {
                    case CHILD_ADDED: {
                        System.out.println("Node added: " + ZKPaths.getNodeFromPath(event.getData().getPath()));
                        break;
                    }

                    case CHILD_UPDATED: {
                        System.out.println("Node changed: " + ZKPaths.getNodeFromPath(event.getData().getPath()));
                        break;
                    }

                    case CHILD_REMOVED: {
                        System.out.println("Node removed: " + ZKPaths.getNodeFromPath(event.getData().getPath()));
                        break;
                    }
                    default:
                        break;
                }
            }
        };
        //添加監聽器
        pathCache.getListenable().addListener(listener);

        pathCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
        System.out.println("Register zk pathCache successfully!");

        Thread.sleep(60000);
        pathCache.close();

        //關閉zk連接
        client.close();
    }

}

Zookeeper應用場景

在這裏插入圖片描述

  1. NameNode使用ZooKeeper實現高可用.
  2. Yarn ResourceManager使用ZooKeeper實現高可用.
  3. 利用ZooKeeper對HBase集羣做高可用配置
  4. kafka使用ZooKeeper
    • 保存消息消費信息比如offset.
    • 用於檢測崩潰
    • 主題topic發現
    • 保持主題的生產和消費狀態
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章