Zookeeper應用場景

blue星空 發表於 2021-08-31
ZooKeeper

 

  • 資料釋出與訂閱:釋出訂閱模型,就是釋出者將資料釋出到ZK節點上,供訂閱者動態獲取資料,實現資料的集中管理和動態更新。
    • 配置中心:在應用中,將全域性的配置資訊放到ZK上集中管理,在應用啟動的時候主動獲取一次配置。同時,在節點上註冊一個watcher,保證每次資料更新時會通知訂閱者更新配置資訊。
    • 後設資料維護:在分散式搜尋服務中,索引的元資訊和伺服器叢集機器的節點狀態儲存在ZK的指定節點上,供各個客戶端使用。
    • 分散式日誌收集系統:這個系統的核心工作是收集不同伺服器的日誌。收集器通常是按照應用來分配任務單元,因此在ZK上用應用名建立一個節點,將需要收集的伺服器的IP註冊到這個節點的子結點上。
  • 命名服務:客戶端應用能夠根據指定名字來獲取資源或服務的地址、提供者等資訊。
  • 分散式通知/協調:ZK中特有的watcher註冊與非同步通知機制,能夠很好的實現分散式環境下不同系統之間的通知與協調,實現對資料變更的實時處理。使用方法通常是:不同系統對ZK上的同一個節點進行註冊,監聽節點的變化。任意一個系統對節點進行了更新,其它系統都能接到通知,並進行相應處理。
    • 任務彙報:類似於任務分發系統。子任務啟動後,在ZK上註冊一個臨時節點,並定時將自己的進度寫入這個節點,這樣任務管理者可以實時檢視任務進度。
    • 伺服器列表維護:能都動態監聽伺服器的上下線
      • 服務端程式碼
Zookeeper應用場景
package zookeeper;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;

public class Server {

    private static final String CONNECT_STRING = "172.17.23.79:2181,172.17.23.79:2182";
    private static final int TIME_OUT = 15000;

    private ZooKeeper zooKeeper;

    public Server() throws IOException {
        this.zooKeeper = new ZooKeeper(CONNECT_STRING, TIME_OUT, watchedEvent -> {

        });
    }

    /**
     * 建立臨時序列節點(伺服器關閉時節點會自動刪除)
     * @param serverName
     * @return
     * @throws Exception
     */
    public String regsterServer(String serverName) throws Exception {
        String result = zooKeeper.create("/clusterServer/server", serverName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println(serverName+" server start....");
        return result;
    }

    public static void main(String[] args) throws Exception {
        Server server = new Server();
        server.regsterServer(args[0]);
        //保持程式執行,防止程式停止執行導致節點自動刪除
        while (true) {

        }
    }
}
View Code
      • 客戶端程式碼
Zookeeper應用場景
package zookeeper;

import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
* 獲取並監聽註冊的伺服器列表
*/
public class Client {

    private static final String CONNECT_STRING = "172.17.23.79:2181,172.17.23.79:2182";
    private static final int TIME_OUT = 15000;

    private ZooKeeper zooKeeper;

    public Client() throws IOException {
        this.zooKeeper = new ZooKeeper(CONNECT_STRING, TIME_OUT, watchedEvent -> {
            System.out.println("watcher works.");
            try {
                getServers();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    public void getServers() throws Exception {
        List<String> servers = new ArrayList<>();
        List<String> children = zooKeeper.getChildren("/clusterServer", true, null);
        for (String child : children) {
            String server = getData("/clusterServer/"+child);
            servers.add(server);
        }
        System.out.println(servers);
    }

    public String getData(String path) throws Exception {
        byte[] data = zooKeeper.getData(path, true, null);
        return new String(data);
    }

    public static void main(String[] args) throws Exception {
        Client client = new Client();
        client.getServers();
        while (true) {

        }
    }
}
View Code
    • 分散式鎖:
      • 保持獨佔:通常的做法是把ZK的節點看作一把鎖,通過create node的方式來實現。
                              具體的實現方式是:
        • 方法一:所有的客戶端都去建立/lock節點,最終建立成功的持有這把鎖。(同一個節點只能建立一次,再次建立會返回失敗資訊)
Zookeeper應用場景
/**
* 通過建立臨時節點,實現伺服器之間的獨佔鎖
*/
@Test
public void singleLock() {
    try {
        //引數:1,節點路徑; 2,要儲存的資料; 3,節點的許可權; 4,節點的型別
        String nodePath = zooKeeper.create("/lock", "This is Lock.".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        //建立成功,則相當於擁有獨佔鎖,可以進行以下邏輯
        //TODO 業務邏輯
        System.out.println(nodePath);
        //業務邏輯結束後,刪除節點,即釋放鎖資源
        zooKeeper.delete("/lock", -1);
    } catch (Exception e) {
        //建立節點失敗,重新呼叫,直至建立成功
        if (e instanceof KeeperException && "NODEEXISTS".equals(((KeeperException)e).code().name())) {
            System.out.println("Node exists.");
            singleLock();
        }else {
            e.printStackTrace();
        }
    }
}
View Code
        • 方法二:判斷/lock節點是否存在,如果存在,說明其它伺服器已經持有鎖資源;如果不存在,則鎖資源處於空閒狀態,建立節點佔有鎖資源。
Zookeeper應用場景
/**
* 通過建立臨時節點,實現伺服器之間的獨佔鎖
*/
@Test
public void singleLock2() throws KeeperException, InterruptedException {
    Stat stat = zooKeeper.exists("/lock", false);
    //如果節點已經存在,等待其它伺服器刪除節點。即:等待其它伺服器釋放鎖資源
    while(stat != null) {  }


    //引數:1,節點路徑; 2,要儲存的資料; 3,節點的許可權; 4,節點的型別
    String nodePath = zooKeeper.create("/lock", "This is Lock.".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
    //建立成功,則相當於擁有獨佔鎖,可以進行以下邏輯
    //TODO 業務邏輯
    System.out.println(nodePath);
    //業務邏輯結束後,刪除節點,即釋放鎖資源
    zooKeeper.delete("/lock", -1);
}
View Code
    • 控制時序:/lock節點已存在,客戶端在它下面建立臨時有序節點/lock/{sessionId}-1 , /lock/{sessionId}-2 , /lock/{sessionId}-3 …..(通過節點屬性控制 CreateMode.EPHEMERAL_SEQUENTIAL控制),保證子節點建立的有序性,從而保證的客戶端的時序性。
      • 方式一:獲取鎖資源時,程式處於block狀態,直到獲取鎖資源。
Zookeeper應用場景
/**
* 通過建立臨時時序節點,實現伺服器之間的時序鎖
*/
@Test
public void lock() throws KeeperException, InterruptedException {
    //建立臨時時序節點
    String nodePath = zooKeeper.create("/lock/sublock", "This is sub lock.".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    
    while(true) {
        List<String> children = zooKeeper.getChildren("/lock", false);
        //排序,並獲取序號最小的節點。(序號越小,表明請求時間越早,優先獲取鎖資源)
        children.sort(String::compareTo);
        if (nodePath.equals("/lock/"+children.get(0))){
            //TODO 業務邏輯
            System.out.println("TODO  Logic.");
            break;
        }
    }

    //業務邏輯結束後,刪除節點,即釋放鎖資源
    zooKeeper.delete(nodePath, -1);
}
View Code
      • 方式二:通過watcher監聽伺服器列表變化,判斷當前伺服器是否獲取鎖資源,程式不會block
Zookeeper應用場景
package zookeeper;


import org.apache.zookeeper.*;


import java.io.IOException;
import java.util.Collections;
import java.util.List;


/**
*
* 通過Zookeeper實現伺服器之間的時序鎖
*
*/
public class SeqLock {

    private static final String CONNECT_STRING = "172.17.23.79:2181,172.17.23.79:2182";

    private static ZooKeeper zooKeeper;
    private String thispath;

    public SeqLock() throws IOException {
        //超時時間單位:毫秒
        zooKeeper = new ZooKeeper(CONNECT_STRING, 15000, event -> {
            //監聽“/lock”的子節點變化。如果有伺服器釋放鎖,判斷自己是否獲取鎖
            if (event.getType() == Watcher.Event.EventType.NodeChildrenChanged
                    && event.getPath().startsWith("/lock")) {
                try {
                    List<String> children = zooKeeper.getChildren("/lock", false);
                    if (children.size() > 0) {
                        Collections.sort(children);
                        String fistNode = "/lock/"+children.get(0);
                        if (fistNode.equals(thispath)){
                            doSomethingAndDelNode();
                        }
                    }


                } catch (KeeperException | InterruptedException e) {
                    e.printStackTrace();
                }


            }
        });
    }


    /**
     * 通過建立臨時時序節點註冊時序鎖,並監聽伺服器列表
     */
    public void lock() throws KeeperException, InterruptedException {
        thispath = zooKeeper.create("/lock/sublock", "This is sub lock.".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println(thispath);
        List<String> children = zooKeeper.getChildren("/lock", false);
        //如果只有一個子節點,說明鎖資源被當前伺服器持有。如果子節點不止一個,說明鎖資源已經被其它伺服器持有
        if(children.size() == 1) {
            doSomethingAndDelNode();
        }
    }

    private void doSomethingAndDelNode() throws InterruptedException, KeeperException {
        //TODO 業務邏輯
        System.out.println("TODO  Logic.");
        //業務邏輯結束後,刪除節點,即釋放鎖資源
        zooKeeper.delete(thispath, -1);
    }

    public static void main(String[] args)  {
        try {
            SeqLock lock = new SeqLock();
            lock.lock();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
View Code

 

參考文獻:
  • ZooKeeper典型應用場景
連結   :https://pan.baidu.com/s/1lohZ98K33z_Hf_8Y370Nuw
提取碼:kydw