zk 基本命令
客戶端命令:
連接指定的zookeeper服務器: sh zkCli.sh -server ip:port
創(chuàng)建節(jié)點: create [-s] [-e] path data acl 其中-s -e分別指定節(jié)點特性,順序或臨時節(jié)點。默認情況下(不添加-s, -e),創(chuàng)建的是持久化節(jié)點。
create /zk-book 123 就是在zookeeper的根節(jié)點下創(chuàng)建了一個叫做/zk-book的節(jié)點,并且節(jié)點的數(shù)據(jù)內(nèi)容是"123",。
讀取
ls path [watch] ls可以列出zookeeper指定節(jié)點下的所有子節(jié)點,
get path [watch] 可以獲取zk指定節(jié)點的數(shù)據(jù)內(nèi)容和屬性信息
set path data [version] 可以更新指定節(jié)點的數(shù)據(jù)內(nèi)容
java客戶端API使用
客戶端可以通過創(chuàng)建一個Zookeeper實例來連接Zookeeper服務器,Zookeeper客戶端與服務端會話的建立是一個異步的過程,構造方法會在處理完客戶端初始化工作后立即返回,此時并沒有真正建立好一個可用的會話,在會話的生命周期處于CONNECTING狀態(tài),當該會話真正創(chuàng)建完畢后,Zookeeper服務端會向會話對應的客戶端發(fā)起一個事件通知,以告知客戶端,客戶端只有在獲取這個通知之后,才算真正建立了會話。構造方法內(nèi)部實現(xiàn)了與zookeeper服務器之間的tcp連接,負責維護客戶端會話的生命周期:
// Java API -> 創(chuàng)建連接 -> 創(chuàng)建一個最基本的ZooKeeper對象實例
public class ZooKeeper_Constructor_Usage_Simple implements Watcher {
/*一個同步輔助類,在完成一組正在其他線程中執(zhí)行的操作之前,它允許一個或多個線程一直等待。
調用 countDown() 的線程打開入口前,所有調用 await 的線程都一直在入口處等待。*/
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
ZooKeeper zookeeper = new ZooKeeper("server-2:2181",
5000, //
new ZooKeeper_Constructor_Usage_Simple());
System.out.println(zookeeper.getState());
try {
connectedSemaphore.await();
} catch (InterruptedException e) {}
System.out.println("ZooKeeper session established.");
}
/**
* 該類實現(xiàn) Watcher 接口,重寫了 process 方法,該方法負責處理來自 Zookeeper
* 服務端的 Watcher 通知,在收到服務端發(fā)來的 SyncConnected 事件之后,解除
* 主程序在 CountDownLatch 上的等待阻塞。至此,客戶端會話創(chuàng)建為完畢。
*/
@Override
public void process(WatchedEvent event) {
System.out.println("Receive watched event:" + event);
if (KeeperState.SyncConnected == event.getState()) {
connectedSemaphore.countDown();
}
}
}
輸出:
Receive watched event:WatchedEvent state:SyncConnected type:None path:null
CONNECTED
ZooKeeper session established.
Disconnected from the target VM, address: '127.0.0.1:52618', transport: 'socket'
還可以復用sessionId和sessionPasswd來創(chuàng)建一個Zookeeper對象實例:
// Java API -> 創(chuàng)建連接 -> 創(chuàng)建一個最基本的ZooKeeper對象實例,復用sessionId和session passwd
public class ZooKeeper_Constructor_Usage_With_SID_PASSWD implements Watcher {
/*一個同步輔助類,在完成一組正在其他線程中執(zhí)行的操作之前,它允許一個或多個線程一直等待。
調用 countDown() 的線程打開入口前,所有調用 await 的線程都一直在入口處等待。*/
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
//創(chuàng)建會話
ZooKeeper zookeeper = new ZooKeeper("server-2:2181",
50000, //
new ZooKeeper_Constructor_Usage_With_SID_PASSWD());
connectedSemaphore.await();
long sessionId = zookeeper.getSessionId();
byte[] passwd = zookeeper.getSessionPasswd();
//使用錯誤的 sessionId 和 SessionPasswd 來創(chuàng)建 Zookeeper 客戶端的實例
zookeeper = new ZooKeeper("server-2:2181",
50000, //
new ZooKeeper_Constructor_Usage_With_SID_PASSWD(),//
1l,//
"test".getBytes());
//使用正確的 sessionId 和 SessionPasswd 來創(chuàng)建 Zookeeper 客戶端的實例
zookeeper = new ZooKeeper("server-2:2181",
50000, //
new ZooKeeper_Constructor_Usage_With_SID_PASSWD(),//
sessionId,//
passwd);
Thread.sleep( Integer.MAX_VALUE );
}
/**
* 該類實現(xiàn) Watcher 接口,重寫了 process 方法,該方法負責處理來自 Zookeeper
* 服務端的 Watcher 通知,在收到服務端發(fā)來的 SyncConnected 事件之后,解除
* 主程序在 CountDownLatch 上的等待阻塞。至此,客戶端會話創(chuàng)建為完畢。
*/
@Override
public void process(WatchedEvent event) {
System.out.println("Receive watched event:" + event);
if (KeeperState.SyncConnected == event.getState()) {
connectedSemaphore.countDown();
}
}
}
output:
Receive watched event:WatchedEvent state:SyncConnected type:None path:null
Receive watched event:WatchedEvent state:Expired type:None path:null
Receive watched event:WatchedEvent state:SyncConnected type:None path:null
第一次使用了錯誤的sessionId和sessionPasswd來創(chuàng)建Zookeeper客戶端實例,結果收到服務端Expired事件通知。第二次使用了正確的sessionId和sessionPasswd來創(chuàng)建客戶端實例,結果連接成功。
創(chuàng)建節(jié)點:
節(jié)點的創(chuàng)建分為同步和異步兩種API:
//ZooKeeper API創(chuàng)建節(jié)點,使用同步(sync)接口。
public class ZooKeeper_Create_API_Sync_Usage implements Watcher {
/*一個同步輔助類,在完成一組正在其他線程中執(zhí)行的操作之前,它允許一個或多個線程一直等待。
調用 countDown() 的線程打開入口前,所有調用 await 的線程都一直在入口處等待。*/
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
//創(chuàng)建會話
ZooKeeper zookeeper = new ZooKeeper("server-2:2181",
5000, //
new ZooKeeper_Create_API_Sync_Usage());
connectedSemaphore.await();
//創(chuàng)建臨時節(jié)點-EPHEMERAL
String path1 = zookeeper.create("/zk-test-ephemeral-",
"".getBytes(),
Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL);
System.out.println("Success create znode: " + path1);
//臨時順序節(jié)點-EPHEMERAL_SEQUENTIAL
String path2 = zookeeper.create("/zk-test-ephemeral-",
"".getBytes(),
Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println("Success create znode: " + path2);
}
/**
* 該類實現(xiàn) Watcher 接口,重寫了 process 方法,該方法負責處理來自 Zookeeper
* 服務端的 Watcher 通知,在收到服務端發(fā)來的 SyncConnected 事件之后,解除
* 主程序在 CountDownLatch 上的等待阻塞。至此,客戶端會話創(chuàng)建為完畢。
*
* WatchedEvent三個基本屬性:通知狀態(tài)keepState、事件類型eventType、節(jié)點路徑path
*
* 服務端在生成WatcheredEvent事件之后,會調用getWrapper方法將自己包裝成一個可序列化的WatcherEvent事件,
* 以便通過網(wǎng)絡傳輸?shù)娇蛻舳恕?蛻舳嗽诮邮艿椒斩说倪@個事件對象后,首先會將WatcherEvent事件還原成一個WatchedEvent
* 事件,并傳遞給process方法處理,回調方法process根據(jù)入?yún)⒕湍軌蚪馕龀鐾暾姆斩耸录恕? */
@Override
public void process(WatchedEvent event) {
if (KeeperState.SyncConnected == event.getState()) {
connectedSemaphore.countDown();
}
}
}
// ZooKeeper API創(chuàng)建節(jié)點,使用異步(async)接口。
public class ZooKeeper_Create_API_ASync_Usage implements Watcher {
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
ZooKeeper zookeeper = new ZooKeeper("server-2:2181",
5000, //
new ZooKeeper_Create_API_ASync_Usage());
connectedSemaphore.await();
//臨時節(jié)點-EPHEMERAL
zookeeper.create("/zk-test-ephemeral-", "".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL,
new IStringCallback(), "I am context.");
//臨時節(jié)點-EPHEMERAL
zookeeper.create("/zk-test-ephemeral-", "".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL,
new IStringCallback(), "I am context.");
//臨時順序節(jié)點-EPHEMERAL_SEQUENTIAL
zookeeper.create("/zk-test-ephemeral-", "".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,
new IStringCallback(), "I am context.");
Thread.sleep( Integer.MAX_VALUE );
}
@Override
public void process(WatchedEvent event) {
if (KeeperState.SyncConnected == event.getState()) {
connectedSemaphore.countDown();
}
}
}
class IStringCallback implements AsyncCallback.StringCallback{
public void processResult(int rc, String path, Object ctx, String name) {
System.out.println("Create path result: [" + rc + ", " + path + ", "
+ ctx + ", real path name: " + name);
}
}
output:
Create path result: [0, /zk-test-ephemeral-, I am context., real path name: /zk-test-ephemeral-
Create path result: [-110, /zk-test-ephemeral-, I am context., real path name: null
Create path result: [0, /zk-test-ephemeral-, I am context., real path name: /zk-test-ephemeral-0000000030
在同步的創(chuàng)建接口中,我們需要關注接口拋出的異常,但是在異步接口中,接口本身是不會拋出異常的,所有的異常都會在回調函數(shù)中通過Result code來體現(xiàn)。
獲取節(jié)點信息
// ZooKeeper API 獲取子節(jié)點列表,使用同步(sync)接口。
public class ZooKeeper_GetChildren_API_Sync_Usage implements Watcher {
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
private static ZooKeeper zk = null;
public static void main(String[] args) throws Exception{
//父節(jié)點路徑
String path = "/zk-book";
//創(chuàng)建Zookeeper會話周期
zk = new ZooKeeper("server-2:2181",
5000, //
new ZooKeeper_GetChildren_API_Sync_Usage());
connectedSemaphore.await();
//創(chuàng)建父節(jié)點
zk.create(path, "".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//創(chuàng)建子節(jié)點
zk.create(path+"/c1", "".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
//獲取父節(jié)點下所有子節(jié)點,同時在接口調用時注冊一個 Watcher,一旦此時有子節(jié)點被創(chuàng)建,
//Zookeeper服務端就會想客戶端發(fā)出一個“子節(jié)點變更”的事件通知,于是,客戶端在收到這個
//時間通知之后就可以再次調用getChildren方法來獲取新的子節(jié)點列表。
List<String> childrenList = zk.getChildren(path, true);
System.out.println(childrenList);
//再次創(chuàng)建子節(jié)點
zk.create(path+"/c2", "".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
Thread.sleep( Integer.MAX_VALUE );
}
@Override
public void process(WatchedEvent event) {
if (KeeperState.SyncConnected == event.getState()) {
if (EventType.None == event.getType() && null == event.getPath()) {
connectedSemaphore.countDown();
} else if (event.getType() == EventType.NodeChildrenChanged) {
try {
System.out.println("ReGet Child:"+zk.getChildren(event.getPath(),true));
} catch (Exception e) {}
}
}
}
}
zk使用場景
數(shù)據(jù)發(fā)布/訂閱
數(shù)據(jù)發(fā)布/訂閱(publish/subscribe)系統(tǒng),即所謂的配置中心,顧名思義就是發(fā)布者將數(shù)據(jù)發(fā)布到zookeeper的一個或一系列節(jié)點上,供訂閱者進行數(shù)據(jù)訂閱,進而達到動態(tài)獲取數(shù)據(jù)的目的,實現(xiàn)配置信息的集中式管理和數(shù)據(jù)的動態(tài)更新。
發(fā)布/訂閱系統(tǒng)一般有兩種設計模式,分別是推push模式和拉pull模式,在推模式中,服務端主動將數(shù)據(jù)更新發(fā)送給所有訂閱的客戶端; 而拉模式則是由客戶端主動發(fā)送請求來獲取最新數(shù)據(jù),通常客戶端都采用定時進行拉取的方式。zookeeper采用的是推拉相結合的方式,客戶端向服務端注冊自己需要關注的節(jié)點,一旦該節(jié)點的數(shù)據(jù)發(fā)生變更,那么服務端就會向相應的客戶端發(fā)送watcher事件通知,客戶端接收到這個消息通知之后,需要主動到服務端獲取最新的數(shù)據(jù)。
如果將配置信息存放到Zookeeper上進行集中管理,通常情況下,應用在啟動的時候都會主動到Zookeeper服務端上進行一次配置信息的獲取,同事,在指定節(jié)點上注冊Watcher監(jiān)聽,這樣只要配置信息發(fā)生變更,服務端都能實時通知到所有訂閱的客戶端,從而達到實時獲取最新配置信息的目的。
命名服務
命名服務是指通過指定的名字來獲取資源或者服務的地址,提供者的信息,比如阿里的dubbo就使用zk來作為其命名服務,維護全局的服務地址列表,服務提供者在啟動的時候,向ZK上的指定節(jié)點/dubbo/${serviceName}/providers目錄下寫入自己的URL地址,這個操作就完成了服務的發(fā)布。服務消費者啟動的時候,訂閱/dubbo/{serviceName}/providers目錄下的提供者URL地址, 并向/dubbo/{serviceName} /consumers目錄下寫入自己的URL地址。所有向ZK上注冊的地址都是臨時節(jié)點,這樣就能夠保證服務提供者和消費者能夠自動感應資源的變化。 通過使用命名服務,客戶端應用能夠根據(jù)指定名字來獲取資源的實體、服務地址和提供者的信息
java語言中的JNDI便是一種典型的命名服務,開發(fā)人員常常使用應用服務器自帶的JNDI實現(xiàn)來完成數(shù)據(jù)源的配置和管理,使用JNDI后,開發(fā)人員可以完全不需要關心與數(shù)據(jù)庫相關的任何信息,包括數(shù)據(jù)庫類型,JDBC驅動類型,用戶名和密碼等。 Zk提供的命名服務功能與JNDI技術類似,能夠幫助應用系統(tǒng)通過一個資源引用的方式實現(xiàn)對資源的定位和使用。在分布式環(huán)境中,上層應用常常需要一個全局唯一的名字,如何使用zk來實現(xiàn)一套分布式全局唯一ID的呢?
全局唯一ID,我們會想到uuid,uuid確實是通用唯一識別碼,非常簡便地保證分布式環(huán)境中的唯一性,一個標準的uuid是一個包含32位字符和4個短線的字符串。雖然uuid很簡便,但uuid存在一些不足的地方,比如生成的字符串長度過長,而且含義不明,根據(jù)生成的字符串開發(fā)人員基本看不出任何其表達的含義,其實我們可以通過調用ZK節(jié)點創(chuàng)建的API接口創(chuàng)建一個順序節(jié)點,并且在API返回值中返回這個節(jié)點的完整名字,這個名字可以包含用戶自定義的前綴,這樣就可以生成全局唯一的id并且含義清楚。
master選舉
在集群的所有機器中選舉出一臺機器作為master,針對這個需求,我們可以選擇常見的關系型數(shù)據(jù)庫中的主鍵特性來實現(xiàn):集群中的所有機器都向數(shù)據(jù)庫中插入一條相同主鍵ID的記錄,數(shù)據(jù)庫會幫助我們自動進行主鍵沖突檢查,在所有進行插入操作的客戶端機器中,只有一臺機器能夠成功,那么我們就認為向數(shù)據(jù)庫中成功插入數(shù)據(jù)的客戶端機器成為master。咋一看,這個方案確實可行,依靠關系型數(shù)據(jù)庫中的主鍵特性確實能夠很好地保證在集群中選舉出唯一的一個master,但是如果選舉出來的master掛了,該怎么處理呢?
顯然,關系型數(shù)據(jù)庫無法通知我們這個事件,而zk可以做到這一點,利用zk的強一致性,能夠很好地保證在分布式高并發(fā)情況下節(jié)點的創(chuàng)建的全局唯一性,也即zk將會保證客戶端無法重復創(chuàng)建一個已經(jīng)存在的數(shù)據(jù)節(jié)點,如果同時有多個客戶端請求創(chuàng)建同一個節(jié)點,那么最終一定只有一個客戶端請求能夠創(chuàng)建成功,利用這個特性,就能很容易在分布式環(huán)境進行master選舉了。 每臺機器向zk上創(chuàng)建一個臨時節(jié)點,只有一臺機器能夠創(chuàng)建成功,成功創(chuàng)建節(jié)點的機器就成為master,其他沒有創(chuàng)建成功的機器在創(chuàng)建的節(jié)點上注冊watcher,用于監(jiān)控當前master機器是否存活,一旦發(fā)現(xiàn)當前master掛了,其余客戶端將會重新進行master選舉。
分布式鎖
排它鎖
在java中,synchronized機制和ReentrantLock用來表示鎖,在zk中,沒有類似于這樣的API可以直接使用,而是通過在zk上的數(shù)據(jù)節(jié)點來表示一個鎖,比如/exclusive_lock/lock節(jié)點就可以被定義為一個鎖,在需要獲取排它鎖時,所有的客戶端試圖通過調用create()接口,在/exclusive_lock節(jié)點下創(chuàng)建臨時子節(jié)點/exclusive_lock/lock,最終只有一個客戶端能夠創(chuàng)建成功,創(chuàng)建成功的客戶端就獲取了鎖,沒有獲取到鎖的客戶端就在/exclusive_lock節(jié)點上注冊一個子節(jié)點變更的Watcher監(jiān)聽,以便實時監(jiān)聽到lock節(jié)點的變更情況。
釋放鎖
- /exclusive_lock/lock是一個臨時節(jié)點,當獲取鎖的客戶端機器發(fā)生宕機,zk上的這個臨時節(jié)點就會被刪除,
- 正常執(zhí)行完業(yè)務邏輯后,客戶端就會自動將自己創(chuàng)建的臨時節(jié)點刪除。
共享鎖
- 創(chuàng)建完節(jié)點后,獲取/shared_lock節(jié)點下的所有子節(jié)點,并對該節(jié)點注冊子節(jié)點變更的Watcher監(jiān)聽
- 確定自己的節(jié)點序號在所有子節(jié)點中的順序
- 對于讀請求,如果沒有比自己序號小的子節(jié)點,或是所有比自己序號小的節(jié)點都是讀請求,那么表明自己已經(jīng)成功獲取到了共享鎖,開始執(zhí)行讀取邏輯
- 對于寫請求,如果自己不是序號最小的節(jié)點,就需要進入等待
- 接收到watcher通知后,重復步驟1
釋放鎖的邏輯和排他鎖是一致的。
當一個客戶端移除自己的共享鎖后,大量的Watcher通知和”子節(jié)點列表獲取“兩個操作重復運行,并且絕大多數(shù)的運行結果都是判斷出自己并非是序號最小的節(jié)點,從而繼續(xù)等待下一次通知,客戶端無端地接收到過多與自己無關的事件通知顯然不太合理,如果在集群規(guī)模比較大的情況,不僅會對zk服務器造成巨大的性能影響和網(wǎng)絡沖擊,如果同一時間多個節(jié)點對應的客戶端完成事務,zk服務器就會在短時間內(nèi)向其余客戶端發(fā)送大量的事件通知,這就造成了羊群效應。造成這一局面的原因在于沒找準客戶端真正的關注點,其實每個客戶端是判斷自己是否是所有子節(jié)點中序號最小的,每個節(jié)點只需要關注比自己序號小的那個相關節(jié)點的變更情況就可以了,而不需要關注全局的子列表變更情況。
zk的應用
zk在yarn中的應用
YARN主要由ResourceManager、NodeManager、ApplicationMaster(AM)和Container四部分組成,RM作為全局的資源管理器,負責整個系統(tǒng)的資源管理和分配,但RM存在單點問題,對此YARN設計了一套Active/Standby模式的ResourceManager HA架構:
RM是基于zk實現(xiàn)的ActiveStandbyElector組件來確定RM的狀態(tài)是Active還是standby,具體做法:
創(chuàng)建鎖節(jié)點, 在zk上會有一個類似/yarn-leader-election/yarn-rm-cluster的鎖節(jié)點,所有的rm在啟動的時候,都會去競爭寫一個lock子節(jié)點,該節(jié)點的類型是臨時節(jié)點,zk能夠保證我們最終只有一個RM能夠創(chuàng)建成功,創(chuàng)建成功的RM就切換為Active狀態(tài),沒有成功的RM就是Standby狀態(tài)。
注冊wather監(jiān)聽 : 所有standby狀態(tài)的RM都會向鎖路徑注冊一個節(jié)點變更的Watcher監(jiān)聽,利用臨時節(jié)點的特性,能夠快速感知到Active狀態(tài)的RM運行狀態(tài)
主備切換: 當Active狀態(tài)的RM出現(xiàn)重啟或者掛掉的異常情況時,其在zk上創(chuàng)建的lock節(jié)點也會隨之被刪除,此時其余各個standby狀態(tài)的RM都會接收到來自zk服務端的watcher事件通知,然后重復步驟1執(zhí)行
此外RM內(nèi)部的一些狀態(tài)信息也是存儲在zk上。
zk在kafka上的應用
Kafka是一個吞吐量極高的分布式消息系統(tǒng),其整體設計是典型的發(fā)布與訂閱模式系統(tǒng),在kafka集群中,沒有中心節(jié)點的概念,集群中的所有服務器是對等的,可以在不做任何配置變更的情況下實現(xiàn)服務器的添加和刪除。雖然broker是分布式部署并且相互之間是獨立運行的(kafka的一些概念這里筆者不做介紹),但還是需要有一個注冊系統(tǒng)能夠將整個集群的broker服務器管理起來,kafka使用了zk,在zk上會有一個專門用來進行broker服務器列表記錄的節(jié)點,路徑為/brokers/ids, 每個broker服務器在啟動時,都會到zk上進行注冊,即到broker節(jié)點下創(chuàng)建自己的節(jié)點,/brokers/ids/[0-N], 不同的broker必須使用不同的broker id進行注冊,創(chuàng)建完broker節(jié)點(同樣是臨時節(jié)點)后,每個broker就會將IP地址和端口號等信息寫入到該節(jié)點去。
在kafka中會將一個topic的消息分成多個分區(qū)并將其分到多個broker上,這些分區(qū)信息和broker的對應關系也是由zk維護的,由專門的節(jié)點來記錄,其節(jié)點路徑為/brokers/topics, kafka中的每一個Topic都會以/brokers/topics/[topic]的形式記錄在這個節(jié)點下, broker服務器在啟動后,會在對應的topic節(jié)點下注冊自己的borker id,并寫入針對該topic自己負責的分區(qū)總數(shù),/brokers/topics/logins/3 ->2 這個節(jié)點表明broker id為3 的一個broker服務器,對于login這個topic消息提供了2個分區(qū)進行消息存儲。
生產(chǎn)者均衡
kafka是分布式部署的broker服務器,會對同一個topic的消息進行分區(qū)并將其分布到不同的broker服務器上,因此,生產(chǎn)者需要將消息合理地發(fā)送到這些分布式的broker上,如何進行負載均衡呢?在每一個broker啟動時,會首先完成broker注冊過程,諸如”有哪些可訂閱的topic的元數(shù)據(jù)信息“, 生產(chǎn)者就能夠通過這個節(jié)點變化來動態(tài)感知到broker服務器列表的變更,kafka的生產(chǎn)者會對zk上broker的增減、topic的增減、broker和topic關聯(lián)關系的變化等事件注冊watcher監(jiān)聽,通過zk的watcher通知,生產(chǎn)者可以動態(tài)獲取broker和topic的變化情況。
消費者均衡
- 每個消費者服務器在啟動的時候,都會到zk的指定節(jié)點下創(chuàng)建一個屬于自己的消費者節(jié)點,例如 /consumers/[group_id]/ids/[consumer_id],完成節(jié)點創(chuàng)建后,消費者就會將自己訂閱的Topic信息寫入該節(jié)點,(這個節(jié)點也是一個臨時節(jié)點,第一單消費者服務器出現(xiàn)故障或者下線就會被刪掉)
- 每個消費者都需要關注所屬消費者組中消費者服務器的變化情況,即對/consumers/[group_id]/ids節(jié)點注冊子節(jié)點變化的Watcher監(jiān)聽,一旦發(fā)現(xiàn)消費者新增或減少,就會觸發(fā)消費者的負載均衡。
- 對broker服務器的變化注冊監(jiān)聽: 消費者需要對/broker/ids[0-N]中的節(jié)點進行監(jiān)聽的注冊,如果發(fā)現(xiàn)broker服務器列表發(fā)生變化,就根據(jù)具體情況來決定是否需要進行消費者的負載均衡。
- 進行消費者負載均衡: 為了能夠讓同一個topic下不同分區(qū)的消息盡量均衡地被多個消費者消費而進行的一個消費者與消息分區(qū)分配的過程。通常,對于一個消費者分組,如果組內(nèi)的消費者服務器發(fā)生變更或broker服務器發(fā)生變更,會觸發(fā)消費者負載均衡。
這里對kafka的具體負載均衡算法不做具體介紹,有興趣的可以查閱相關資料。
zk在dubbo中的應用
我們以服務”com.foo.BarService“這個服務來做具體介紹:
服務提供者: 服務提供者在初始化啟動的時候,會首先在zookeeper的/dubbo/com.foo.BarService/providers節(jié)點下創(chuàng)建一個子節(jié)點,并寫入自己的URL地址,這就代表了”com.foo.BarService“這個服務的一個提供者。
服務消費者
服務消費者會在啟動的時候,讀取并訂閱zk上/dubbo/com.foo.BarService/providers節(jié)點下的所有子節(jié)點,并解析出所有提供者的url地址作為該服務地址列表,然后開始發(fā)起正常調用。同時,服務消費者還會在zk的/dubbo/com.foo.BarService/consumers節(jié)點下創(chuàng)建一個臨時節(jié)點,并寫入自己的url地址,這就代表了com.foo.BarService這個服務的一個消費者。
監(jiān)控中心
監(jiān)控中心是dubbo中服務治理體系的重要一環(huán),它需要知道一個服務的所有提供者和訂閱者,以及變化情況,監(jiān)控中心在啟動的時候,會通過zk的/dubbo/com.foo.BarService節(jié)點來獲取所有提供者和消費者的URL地址,并注冊Watcher來監(jiān)聽其子節(jié)點變化。