zookeeper的介紹(初步認識)

一、zookeeper是什麼
集中式存儲數據服務,通過監聽通知機制來實現來實現分佈式應用的協調。

1、目前比較常見應用場景有:
分佈式鎖(臨時節點)
服務註冊與訂閱(共用節點)
分佈式通知(監聽znode)
服務命名(znode特性)
數據訂閱、發佈(watcher)

2、zookeeper數據模型
在這裏插入圖片描述

zookeeper 維護了一個類似文件系統的數據結構,每個子目錄(/znode1、/znode1/leaf)都被稱作爲 znode 即節點。和文件系統一樣,我們可以很輕鬆的對 znode 節點進行增加、刪除等操作,而且還可以在一個znode下增加、刪除子znode,區別在於文件系統的是,znode可以存儲數據(嚴格說是必須存放數據,默認是個空字符)。

這些節點分爲4中類型:

1、持久化節點(zk斷開節點還在)

2、持久化順序編號目錄節點

3、臨時目錄節點(客戶端斷開後節點就刪除了)

4、臨時目錄編號目錄節點

zkClient鏈接zookeeper,首先執行如下命令,連接到zookeeper server
./zkCli.sh -server ip:2181

./zkCli.sh -server 192.168.25.128:2181

在這裏插入圖片描述

創建持久化節點

create /zk_test

創建臨時節點

create -e /zk_test/e_node

創建臨時順序化節點

create -e -s /zk_test/es_node

創建順序化節點

create -s /zk_test/s_node

在這裏插入圖片描述
退出重新進入,臨時節點消失。
在這裏插入圖片描述
在這裏插入圖片描述

1、分佈式鎖

zookeeper基於watcher機制和znode的有序節點。首先創建一個/zk_test/lock父節點作爲一把鎖,儘量是持久節點(PERSISTENT類型),每個嘗試獲取這把鎖的客戶端,在/zk_test/lock父節點下創建臨時順序子節點。

由於序號的遞增性,我們規定序號最小的節點即獲得鎖。例如:客戶端來獲取鎖,在/zk_test/lock節點下創建節點爲/zk_test/lock/seq-00000001,它是最小的所以它優先拿到了鎖,其它節點等待通知再次獲取鎖。/test/lock/seq-00000001執行完自己的邏輯後刪除節點釋放鎖。

那麼節點/test/lock/seq-00000002想要獲取鎖等誰的通知呢?

這裏我們讓/test/lock/seq-00000002節點監聽/test/lock/seq-00000001節點,一旦/test/lock/seq-00000001節點刪除,則通知/test/lock/seq-00000002節點,讓它再次判斷自己是不是最小的節點,是則拿到鎖,不是繼續等通知。

以此類推/test/lock/seq-00000003節點監聽/test/lock/seq-00000002節點,總是讓後一個節點監聽前一個節點,不用讓所有節點都監聽最小的節點,避免設置不必要的監聽,以免造成大量無效的通知,形成“羊羣效應”。

zookeeper分佈式鎖和redis分佈式鎖相比,因爲大量的創建、刪除節點性能上比較差,並不是很推薦。

public class Zk implements Lock,Runnable {


    private static final String IP_PORT = "192.168.25.128:2181";
    private static final String Z_NODE = "/lock1";

    // private volatile String beforePath;
    private static ThreadLocal<String> beforePathLocal = new ThreadLocal<>();
    private volatile String path;

    private ZkClient zkClient = new ZkClient(IP_PORT);

    public Zk() {
        if (!zkClient.exists(Z_NODE)) {
            zkClient.createPersistent(Z_NODE);
        }
    }

    @Override
    public void lock() {
        if (tryLock()) {
            System.out.println("獲得鎖");
        } else {
            // 嘗試加鎖
            // 進入等待 監聽
            waitForLock();
            // 再次嘗試
            lock();
        }

    }
    @Override
    public synchronized boolean tryLock() {
        // 第一次就進來創建自己的臨時節點,(如果已經有其他節點創建,下面獲取節點更大)
        if (StringUtils.isBlank(path)) {
            path = zkClient.createEphemeralSequential(Z_NODE + "/", "lock");
        }

        // 對節點排序
        List<String> children = zkClient.getChildren(Z_NODE);
        //節點排序
        Collections.sort(children);

        // 當前的是最小節點說明當前節點,
        if (path.equals(Z_NODE + "/" + children.get(0))) {
            System.out.println("當前路徑是創建最早,鎖這個節點");
            return true;
        } else {
            // 不是最小節點 就找到自己的前一個 ,用於監聽上一個節點,而不是最小節點,防止羊羣效應。
            // 依次類推 釋放也是一樣
            int i = Collections.binarySearch(children, path.substring(Z_NODE.length() + 1));
            //獲取當前節點上一個節點地址
            String beforePath = Z_NODE + "/" + children.get(i - 1);
            beforePathLocal.set(beforePath);
        }
        return false;
    }

    @Override
    public void unlock() {
        //防止內存泄漏
        beforePathLocal.remove();
        zkClient.delete(path);
    }

    public void waitForLock() {
         CountDownLatch cdl = new CountDownLatch(1);

        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {
            }
            @Override
            public void handleDataDeleted(String s) throws Exception {
                System.out.println(Thread.currentThread().getName() + ":監聽到節點被刪(由於這裏監聽是該路徑的上一個節點,所以是上一個節點被刪)");
                cdl.countDown();
            }
        };
        // 監聽
        String beforePath = beforePathLocal.get();
        this.zkClient.subscribeDataChanges(beforePath, listener);
        if (zkClient.exists(beforePath)) {
            try {
                System.out.println("加鎖失敗 等待");
                //一直等到 該節點監聽上一個節點被刪
                cdl.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 釋放監聽
        zkClient.unsubscribeDataChanges(beforePath, listener);
    }

    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    public void lockInterruptibly() throws InterruptedException {

    }

    public Condition newCondition() {
        return null;
    }
    static volatile int inventory = 10;

    @Override
    public void run() {
        Zk zk = new Zk();
        try {
            zk.lock();
            if (inventory > 0) {
                inventory--;
            }
            System.out.println(inventory);
            return;
        } finally {
            zk.unlock();
            System.out.println("釋放鎖");
        }
    }

    private static final int NUM = 5;
    public static void main(String[] args) {
        try {

            for (int i = 1; i <= NUM; i++) {
                new Thread(new Zk()).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

2、配置管理

現在有很多開源項目都在使用Zookeeper來維護配置,像消息隊列Kafka中,就使用Zookeeper來維護broker的信息;dubbo中管理服務的配置信息。原理也是基於watcher機制,例如:創建一個/config節點存放一些配置,客戶端監聽這個節點,一點修改/config節點的配置信息,通知各個客戶端數據變更重新拉取配置信息。

可以看到dubbo在註冊provider信息,任然是創建節點
AbstractZookeeperClient

  public void create(String path, boolean ephemeral) {
        if (!ephemeral) {
            if (this.persistentExistNodePath.contains(path)) {
                return;
            }

            if (this.checkExists(path)) {
                this.persistentExistNodePath.add(path);
                return;
            }
        }

        int i = path.lastIndexOf(47);
        if (i > 0) {
            this.create(path.substring(0, i), false);
        }

        if (ephemeral) {
            this.createEphemeral(path);
        } else {
            this.createPersistent(path);
            this.persistentExistNodePath.add(path);
        }

    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章