ZkClient解決了watcher的一次性注冊問題,將znode的事件重新定義為子節點的變化、數據的變化、連接狀態的變化三類,有ZkClient統一將watcher的WatchedEvent轉換到以上三種情況中去處理,watcher執行后重新讀取數據的同時,在注冊新的相同的watcher。
1.簡單的使用ZkClient
public static void main( String[] args ) { //連接Zookeeper ZkClient zkClient = new ZkClient("127.0.0.1:2181"); //創建節點 zkClient.create("/root", "server data", CreateMode.PERSISTENT); //創建子節點 zkClient.create("/root/server", "server data", CreateMode.EPHEMERAL); //獲取子節點 List<String> children = zkClient.getChildren("/root"); //編輯子節點 for (String item : children) { System.out.println(item); } //獲取子節點的數量 Integer number = zkClient.countChildren("/root"); //判斷節點是否存在 if(zkClient.exists("/root")){ System.out.println("存在"); }; //寫入數據 zkClient.writeData("/root/child", "哈哈哈"); //讀取節點數據 Object object = zkClient.readData("/root/child"); //刪除節點 zkClient.delete("/root/child"); }
ZkClient將Zookeeper的watcher機制轉化為一種更加容易理解的訂閱形式,並且這種關系是可以保持的,而非一次性的。也就是說子節點的變化、數據的變化、狀態的變化是可以訂閱的。當watcher使用完后,zkClient會自動增加一個相同的watcher。
zkClient.subscribeChildChanges("/root", new IZkChildListener() { @Override public void handleChildChange(String arg0, List<String> arg1) throws Exception { // TODO Auto-generated method stub System.out.println("子節點的變化"); } }); zkClient.subscribeDataChanges("/root", new IZkDataListener() { @Override public void handleDataDeleted(String arg0) throws Exception { // TODO Auto-generated method stub System.out.println("數據被刪除"); } @Override public void handleDataChange(String arg0, Object arg1) throws Exception { // TODO Auto-generated method stub System.out.println("數據被更改"); } }); zkClient.subscribeStateChanges(new IZkStateListener() { @Override public void handleStateChanged(KeeperState arg0) throws Exception { // TODO Auto-generated method stub System.out.println("數據狀態改變"); } @Override public void handleNewSession() throws Exception { // TODO Auto-generated method stub System.out.println("當session expire異常重新連接時,由於原來的所有的watcher和EPHEMERAL節點都已失效,可以在這里進行相應的容錯處理"); } });
2.路由和負載均衡的實現
當服務越來越多,規模越來越大,機器的數量也會越來越大,如果純靠人工來管理和維護服務及地址的信息,已經越來越困難了。並且,依賴單一的硬件負載均衡設備或者使用LVS和Ngnix等軟件方案來解決進行路由和負載均衡調度,一旦服務路由或者服務器宕機,所依賴的所有服務都將失效。如果采用雙機高可用的部署方案,使用一台服務器“stand by”,能部分解決問題,但是鑒於負載均衡設備的昂貴成本,也難以全面推廣。
一旦服務器與Zookeeper集群斷開連接,節點就不存在了,通過注冊相應的watcher,服務消費者能夠第一時間獲知提供者機器信息的變更。利用其znode的特點和watcher記住,將作為動態注冊和獲取服務信息的配置中心,統一管理服務名稱和其對應的服務列表消息。我們近乎實時的感知到后端服務器的狀態(上線、下線、宕機)。Zookeeper集群之間通過zab協議,服務配置信息能夠保持一致,而Zookeeper本身的容錯特性和leader選舉機制,能保障我們方便地進行擴容。
//基於Zookeeper所實現的服務消費者獲取服務提供者地址列表的關鍵代碼 消費者 ZkClient zkClient = new ZkClient("127.0.0.1:2181"); String serviceName = "service-B"; String zkServiceList = "127.0.0.1:2181"; String SERVICE_PATH = "/configcenter/"+serviceName; //判斷是否存在 boolean serviceExists = zkClient.exists(SERVICE_PATH); //如果存在,獲取地址列表 if(serviceExists){ serviceList = zkClient.getChildren(SERVICE_PATH); }else{ throw new RuntimeException("節點不存在"); } //注冊事件監聽 zkClient.subscribeChildChanges(SERVICE_PATH, new IZkChildListener() { @Override public void handleChildChange(String arg0, List<String> currentChilds) throws Exception { // TODO Auto-generated method stub //返回新的列表 serviceList = currentChilds; } });
這段代碼實例化了一個zkClient對象,判斷服務名稱是否已經注冊,如果還沒有注冊該節點,則表明沒有該服務,繼而拋出異常。如果存在則獲取其所有子節點獲取地址后就可以通過負載均衡算法,選出一台服務器,發起遠程調用。服務器列表緩存在本地只有當地址列表發生變化時,才需要更新該列表,以降低網絡開銷。這就是注冊一個 subscribeChildChanges 的作用。
3.服務提供者想Zookeeper集群注冊的部分關鍵代碼:
//服務提供者想Zookeeper集群注冊服務的關鍵代碼 服務者 ZkClient zkClient = new ZkClient("127.0.0.1:2181"); String PATH = "/configcenter";//根節點路徑 //判斷是否存在 boolean rootExists = zkClient.exists(PATH); //如果存在,獲取地址列表 if(!rootExists){ zkClient.createPersistent(PATH); } String serviceName = "service-c"; boolean serviceExists = zkClient.exists(PATH+"/"+serviceName); if(!serviceExists){ zkClient.createPersistent((PATH+"/"+serviceName)); } InetAddress address = InetAddress.getLocalHost(); //創建當前服務器節點 String ip = address.getHostAddress().toString(); //創建當前服務器節點 zkClient.createEphemeral(PATH+"/"+serviceName+"/"+ip); for (String item : zkClient.getChildren("/configcenter")) { System.out.println(item); }