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 效果如下: