1. 程式人生 > >zookeeper分散式服務叢集與負載監控講解

zookeeper分散式服務叢集與負載監控講解

ZooKeeper是一個分散式的,開放原始碼的分散式應用程式協調服務,是Google的Chubby一個開源的實現,是Hadoop和Hbase的重要元件。它是一個為分散式應用提供一致性服務的軟體,提供的功能包括:配置維護、域名服務、分散式同步、組服務等。

對於zookeeper的安裝,大家可以先按照zookeeper官網上的介紹進行安裝,因為直接在網上找到的安裝步驟可能會存在問題,所以大家應該養成一個良好的習慣,儘量在官方網站獲取最權威的介紹與知識,就向我們都原始碼是一樣的。官方的價值是最高的。

重點zookeeper是一個開源的進行協調服務的中介軟體,高效能,分散式。它能做什麼呢?資料儲存、服務註冊與發現、叢集管理、分散式鎖等。而且zookeeper的部署支援單機和叢集,具有一定的靈活性。

zookeeper的底層資料結構是樹形結構,每一個節點成為資料節點,注意,節點儲存的是資料,而不是檔案或者其他。資料節點成為znode,它是zookeeper結構的最小組成單元。

建立節點的命令是:

  -s : 有序節點  無須節點

 -e : 臨時節點 持久節點

create -s -e path data acl

節點的特性有兩種:一個是臨時性節點,它的生命週期與當前該節點所屬的會話相關聯,在會話結束後的一段時間後,臨時性節點會自動刪除。這裡注意的一點是為什麼是會話結束一段時間之後才刪除節點呢?是因為設計者在設計的時候考慮的網路抖動的問題,這可能不是人為的結束會話,而是由於網路的故障或者其他的故障,當值當前的會話被迫結束,如果立即刪除調zookeeper中儲存的節點資料,顯然,這不是我們想要的,所以,在設計的時候,考慮到網路心跳的問題,在會話結束的一段時間後,資料節點才會通過某些演算法執行自動刪除。在臨時行節點的資料中有一個屬性欄位是EphemeralOwner,它儲存的是該節點所屬會話的會話id,是一個唯一的值。另一個是持久化節點,它在建立之後,不會隨著會話的宣告週期而影響自己的生命週期。當建立節點時,不寫 -s -e 引數的時候,預設建立的是持久化節點。

另外需要注意的是,只有持久化節點才可以建立子節點,臨時節點是不可以建立子節點的。zookeeper的樹形結構在深度上是無限制的,在廣度上一般也沒有限制。節點名稱在同一級目錄下必須唯一。

通過get命令,我們可以獲取到該節點的資料資訊。

下面看一下這些屬性都分別表示什麼:

cZxid : 表示建立事務id
ctime :表是常見時間
mzxid : 表示修改事務時間 
pzxid : 只有子節點列表變更才會更新
cversion : 與樂觀鎖相關  任何客戶端對資料庫欄位修改之後 對應的欄位遞增

下面通過程式來實現一下結構的構建與服務的呼叫:

目前通過java api 連線zookeeper服務端的方式有兩種 : zkClient 和 curator 我們採用第一種,程式碼如下:

服務提供者 A 與 B 程式碼相同 :

package com.jd.zk;

import com.sun.xml.internal.ws.resources.ProviderApiMessages;
import org.I0Itec.zkclient.ZkClient;

import java.io.IOException;

public class ProviderA {
    private String serviceName = "serviceA";
    private final String ROOT = "/configcenter";
    public void init(){
        // 產生連線 判斷根節點是否存在  不存在則建立
        String zkServer = "192.168.11.142:2181";
        ZkClient zkClient = new ZkClient(zkServer);
        if(!zkClient.exists(ROOT)){
            zkClient.createPersistent(ROOT);
        }
        // 啟動服務 判斷當前服務節點是否註冊過
        if(!zkClient.exists(ROOT + "/" + serviceName)){
            zkClient.createPersistent(ROOT + "/" + serviceName);  // 注意是全路徑
        }
        String ip = "192.128.11.130:8080";
        zkClient.createEphemeral(ROOT + "/" + serviceName + "/" + ip);
        System.out.println("providerA服務啟動成功");
    }

    public static void main(String[] args) throws IOException {
        ProviderA providerA = new ProviderA();
        providerA.init();
        System.in.read();
    }
}

消費者呼叫服務:

package com.jd.zk;


import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.ZkClient;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 消費者 呼叫配置中心的地址  來訪問生產者
 *  從根節點開始讀取
 *
 */

public class Consumer {
    private List<String> serverList = new ArrayList<String>();
    private String serviceName = "serviceA";

    public void init() throws Exception {
        String zkServer = "192.168.11.142:2181";
        ZkClient zkClient = new ZkClient(zkServer);
        String servicePath = "/configcenter/" + serviceName;
        boolean isExist = zkClient.exists(servicePath);
        if(isExist){
            serverList = zkClient.getChildren(servicePath);
        }else{
            throw new Exception("Errow");
        }
        // 實現服務註冊監聽發現
        zkClient.subscribeChildChanges(servicePath, new IZkChildListener() {
            public void handleChildChange(String s, List<String> list) throws Exception {
                System.out.println("服務節點發生變化,節點的資訊 : " + list);
                serverList = list;
            }
        });
    }
    // 實現負載均衡與監聽  只不過這裡採取的是隨機負載
    public void consumer(){
        Random random = new Random();
        int i = random.nextInt(serverList.size());
        System.out.println(i);
        System.out.println("呼叫 :" + serverList.get(i) + "提供服務" );
    }

    public static void main(String[] args) throws Exception {
        Consumer consumer = new Consumer();
        consumer.init();
        consumer.consumer();
        System.in.read();
    }
}

先啟動providerA 和 providerB ,然後執行Consumer 效果如下: