使用情境
整個系統中分master和server兩種角色,每個master、server都需要知道叢集內當前可用的server列表。
原處理方式:master負責叢集server資訊的收集和分發。server啟動後和master建立長串連並心跳檢測,master收集server的新增和disconnect,並將收集到的資訊分發給所有server。
原存在問題:master和server耦合較重;master比較難擴充。
使用方式概述
每個server對應一個Zookeeper的一個子節點,所有master和push通過擷取所有子節點取得server列表,當server disconnect時Zookeeper通知所有機器重新擷取Server列表。
詳細
根節點路勁:/service/serverlist
1.初始化Zookeeper串連
publicstatic void initZk() { try{ #####初始化,如果當前有alive的zk串連就不初始化了 if(zk == null|| !zk.getState().isAlive()) { synchronized(obj) { if(zk == null|| !zk.getState().isAlive()) { ###把之前的close掉 closeZk(); PropertyReader p = newPropertyReader(ZK_PROPERTIES_FILE); String zkServerStr = p.getValueAsString("zookeeper.server", ZK_SERVER_STR_DEFAULT); intsessionTimeout = p.getValueAsInt("zookeeper.sessionTimeout", ZK_SESSION_TIMEOUT); ###重建立立串連 zk = newZooKeeper(zkServerStr, sessionTimeout,newDefaultWatcher()); } } } }catch(Exception e) { thrownew RuntimeException("Zookeeper Init Error!", e); } }
2.server在啟動時添加代表自己的臨時節點並watch其父節點
####如果之前myNodeName已經存在,則刪除ZkUtils.deleteNode(myNodeName);####先看看父節點有沒有,沒有就建立下,父節點得是持久的ZkUtils.createNode(rootNode,CreateMode.PERSISTENT);####添加對父節點child變動的watcherZkUtils.addChildWatch(rootNode,ServerListWatcher.getInstance());###建立自己的節點mynodeZkUtils.createNode(myNodeName,CreateMode.EPHEMERAL);
3.master在啟動時watch父節點
####先看看父節點有沒有,沒有就建立下,父節點得是持久的ZkUtils.createNode(rootNode,CreateMode.PERSISTENT);####添加對父節點child變動的watcherZkUtils.addChildWatch(rootNode,ServerListWatcher.getInstance());
4.監聽該父節點的child變化並處理
publicvoid process(WatchedEvent event) { try{ ###如果是expired,需要重新初始化一遍 if(event.getState() == KeeperState.Expired) { while(true) { try{ // 重新初始化zk串連 ZkUtils.initZk(); // 重新初始化相關zk節點 PushZkUtils.initPushZkNodes(); // 擷取nodeList getAndSaveNodeList(); // 如果成功,則break; break; }catch(RuntimeException e) { AsyncLogger.getInstance().info(LogManager.SERVICE_LOG,"ServerListWatcher:Init Error!"); // 5s後重新執行 Thread.sleep(5* 1000); } } } ###如果是斷開重連,需要重新擷取下list,以免丟失事件 if(event.getState() == KeeperState.SyncConnected) { getAndSaveNodeList(); } ###如果是child變化,及PushServer列表有變,則擷取列表,儲存本地 if(event.getType() == EventType.NodeChildrenChanged) { AsyncLogger.getInstance().info(LogManager.SERVICE_LOG,"ServerListWatcher:Node Child Changed Event Received"); // 儲存到本地,等待同步線程檢查 getAndSaveNodeList(); } }catch(Exception e) { } }
注意事項watcher的使用
1.watcher分類
watcher主要有以下添加方式,不同添加方式監控不同的動作。( 設定watcher時,如果對應服務端已經不存在node,watcher是不會留在服務端)
###擷取節點資料,第一個方法中watch=true代表使用zookeeper建立時傳入的預設watcher,監控動作sets data on the node, or deletes the node.publicbyte[] getData(String path, booleanwatch, Stat stat)publicbyte[] getData(finalString path, Watcher watcher, Stat stat) ###判斷節點是否存在,第一個方法中watch=true代表使用zookeeper建立時傳入的預設watcher,監控動作creates/delete the node or sets the data on the node.publicStat exists(finalString path, Watcher watcher)publicStat exists(String path, booleanwatch)###擷取節點child,第一個方法中watch=true代表使用zookeeper建立時傳入的預設watcher,監控動作deletes the node of the given path or creates/delete a child under the nodepublicList<String> getChildren(finalString path, Watcher watcher)publicList<String> getChildren(String path, booleanwatch)
2.watcher的使用注意
下面幾個點需要在使用的時候多加註意
- watcher是一次性的,每次處理事件之後需要重新加上。在處理和重新加上的間隔之間事件變化無法得知。
###擷取的時候把watcher重新加一次List<String> nodes = ZkUtils.getChildren(ZkUtils.ZK_PUSH_SERVER_LIST_ROOT_NODE,ServerListWatcher.getInstance());
- 用戶端與zookeeper Disconnect時發生的事件可能會丟失。
###如果是斷開重連,需要重新擷取下list,以免丟失事件 if(event.getState() == KeeperState.SyncConnected) { getAndSaveNodeList(); }
- 如果session到期,所有watcher就木有了。
if(event.getState() == KeeperState.Expired) { while(true) { try{ // 重新初始化zk串連 ZkUtils.initZk(); // 重新初始化相關zk節點 PushZkUtils.initPushZkNodes(); // 擷取nodeList getAndSaveNodeList(); // 如果成功,則break; break; }catch(RuntimeException e) { AsyncLogger.getInstance().info(LogManager.SERVICE_LOG,"ServerListWatcher:Init Error!"); // 5s後重新執行 Thread.sleep(5*1000); } }
CONNECTIONLOSS和SESSIONEXPIRED
因為網路環境等原因,這兩個異常還是會時不時出現一下,需要對這些情況有所考慮。
ConnectionLoss表示認為該串連不可用,zk的java client會自動拿著當前的sessionId去其他的zkServer建立串連。
如果在SessionTimeout的時間之內重建立立串連成功,則一切恢複正常,臨時節點和watcher不會發生丟失。
如果在SessionTimeout時間之後才串連上zkServer,則zkServer會返回Session Expired,這個時候前一個session不可用,臨時節點和watcher丟失,需要手動去沖新進行初始化。
ps:Zk執行個體化的時候會傳入一個SessionTimeOut的參數,zk服務端在接收到這個參數後會和本地配置minSessionTimeout(預設2*tickTime)和maxSessionTimeout(預設20*tickTime)進行比較並進行截取。