zookeeper(四)核心watch和watcher
zookeeper有watch事件,是一次性触发的,当watch监视的数据发生变化时,通知设置了该watch的client,即watcher。
同样,其watcher是监听数据发送了某些变化,那就一定会有对应的事件类型,和状态类型。
事件类型:(znode节点相关的)
EventType.NodeCreated
EventType.NodeDataChanged
EventType.NodeChildrenChanged
EventType.NodeDeleted
状态类型:(是跟客户端实例相关的)
KeeperState.Disconnected
KeeperState.SyncConnected
KeeperState.AuthFailed
KeeperState.Expired
wather的特性:一次性,客户端串行执行,轻量。
一次性:对于ZK的watcher,你只需要记住一点:zookeeper有watch事件,是一次性触发的,当watch监视的数据发生变化时,通知设置了该watch的client,即watcher,由于zookeeper的监控都是一次性的,所以每次都必须监控。
客户端串行执行:客户端watcher回调的过程是一个串行同步的过程,这为我们保证了顺序,同时需要开发人员注意一点,千万不用因为一个watcher的处理逻辑影响了整个客户端的watcher回调。
轻量:WatcherEvent是Zookeeper整个Watcher通知机制的最小通知单元。整个单元结构只包含三部分:通知状态,事件类型和节点路径。也就是说Watcher通知非常的简单,只会告诉客户端发生了事件而不会告知其具体内容,需要客户自己去进行获取,而不会直接提供具体的数据内容。
我们通过一个示例,详细学习下Watcher的概念和其目的。Watcher示例:
【ZookeeperWatcher】
1 package bhz.zookeeper.watcher; 2 3 import java.util.List; 4 import java.util.concurrent.CountDownLatch; 5 import java.util.concurrent.atomic.AtomicInteger; 6 7 import org.apache.zookeeper.CreateMode; 8 import org.apache.zookeeper.WatchedEvent; 9 import org.apache.zookeeper.Watcher; 10 import org.apache.zookeeper.Watcher.Event.EventType; 11 import org.apache.zookeeper.Watcher.Event.KeeperState; 12 import org.apache.zookeeper.ZooDefs.Ids; 13 import org.apache.zookeeper.ZooKeeper; 14 import org.apache.zookeeper.data.Stat; 15 16 /** 17 * Zookeeper Wathcher 18 * 本类就是一个Watcher类(实现了org.apache.zookeeper.Watcher类) 19 * @author(alienware) 20 * @since 2015-6-14 21 */ 22 public class ZooKeeperWatcherYuCong implements Watcher { 23 24 /** 定义原子变量 */ 25 AtomicInteger seq = new AtomicInteger(); 26 /** 定义session失效时间 */ 27 private static final int SESSION_TIMEOUT = 10000; 28 /** zookeeper服务器地址 */ 29 private static final String CONNECTION_ADDR = "127.0.0.1:2181"; 30 /** zk父路径设置 */ 31 private static final String PARENT_PATH = "/p"; 32 /** zk子路径设置 */ 33 private static final String CHILDREN_PATH = "/p/c"; 34 /** 进入标识 */ 35 private static final String LOG_PREFIX_OF_MAIN = "【Main】"; 36 /** zk变量 */ 37 private ZooKeeper zk = null; 38 /**用于等待zookeeper连接建立之后 通知阻塞程序继续向下执行 */ 39 private CountDownLatch connectedSemaphore = new CountDownLatch(1); 40 41 /** 42 * 创建ZK连接 43 * @param connectAddr ZK服务器地址列表 44 * @param sessionTimeout Session超时时间 45 */ 46 public void createConnection(String connectAddr, int sessionTimeout) { 47 this.releaseConnection(); 48 try { 49 //this表示把当前对象进行传递到其中去(也就是在主函数里实例化的new ZooKeeperWatcher()实例对象) 50 zk = new ZooKeeper(connectAddr, sessionTimeout, this); 51 System.out.println(LOG_PREFIX_OF_MAIN + "开始连接ZK服务器"); 52 connectedSemaphore.await(); 53 } catch (Exception e) { 54 e.printStackTrace(); 55 } 56 } 57 58 /** 59 * 关闭ZK连接 60 */ 61 public void releaseConnection() { 62 if (this.zk != null) { 63 try { 64 this.zk.close(); 65 } catch (InterruptedException e) { 66 e.printStackTrace(); 67 } 68 } 69 } 70 71 /** 72 * 创建节点 73 * @param path 节点路径 74 * @param data 数据内容 75 * @return 76 */ 77 public boolean createPath(String path, String data, boolean needWatch) { 78 try { 79 //设置监控(由于zookeeper的监控都是一次性的所以 每次必须设置监控) 80 this.zk.exists(path, needWatch); 81 System.out.println(LOG_PREFIX_OF_MAIN + "节点创建成功, Path: " + 82 this.zk.create( /**路径*/ 83 path, 84 /**数据*/ 85 data.getBytes(), 86 /**所有可见*/ 87 Ids.OPEN_ACL_UNSAFE, 88 /**永久存储*/ 89 CreateMode.PERSISTENT ) + 90 ", content: " + data); 91 } catch (Exception e) { 92 e.printStackTrace(); 93 return false; 94 } 95 return true; 96 } 97 98 /** 99 * 读取指定节点数据内容 100 * @param path 节点路径 101 * @return 102 */ 103 public String readData(String path, boolean needWatch) { 104 try { 105 System.out.println("读取数据操作..."); 106 return new String(this.zk.getData(path, needWatch, null)); 107 } catch (Exception e) { 108 e.printStackTrace(); 109 return ""; 110 } 111 } 112 113 /** 114 * 更新指定节点数据内容 115 * @param path 节点路径 116 * @param data 数据内容 117 * @return 118 */ 119 public boolean writeData(String path, String data) { 120 try { 121 System.out.println(LOG_PREFIX_OF_MAIN + "更新数据成功,path:" + path + ", stat: " + 122 this.zk.setData(path, data.getBytes(), -1)); 123 } catch (Exception e) { 124 e.printStackTrace(); 125 return false; 126 } 127 return true; 128 } 129 130 /** 131 * 删除指定节点 132 * 133 * @param path 134 * 节点path 135 */ 136 public void deleteNode(String path) { 137 try { 138 this.zk.delete(path, -1); 139 System.out.println(LOG_PREFIX_OF_MAIN + "删除节点成功,path:" + path); 140 } catch (Exception e) { 141 e.printStackTrace(); 142 } 143 } 144 145 /** 146 * 判断指定节点是否存在 147 * @param path 节点路径 148 */ 149 public Stat exists(String path, boolean needWatch) { 150 try { 151 return this.zk.exists(path, needWatch); 152 } catch (Exception e) { 153 e.printStackTrace(); 154 return null; 155 } 156 } 157 158 /** 159 * 获取子节点 160 * @param path 节点路径 161 */ 162 private List<String> getChildren(String path, boolean needWatch) { 163 try { 164 System.out.println("读取子节点操作..."); 165 return this.zk.getChildren(path, needWatch); 166 } catch (Exception e) { 167 e.printStackTrace(); 168 return null; 169 } 170 } 171 172 /** 173 * 删除所有节点 174 */ 175 public void deleteAllTestPath(boolean needWatch) { 176 if(this.exists(CHILDREN_PATH, needWatch) != null){ 177 this.deleteNode(CHILDREN_PATH); 178 } 179 if(this.exists(PARENT_PATH, needWatch) != null){ 180 this.deleteNode(PARENT_PATH); 181 } 182 } 183 184 /** 185 * 收到来自Server的Watcher通知后的处理。 186 */ 187 @Override 188 public void process(WatchedEvent event) { 189 190 System.out.println("进入 process 。。。。。event = " + event); 191 192 try { 193 Thread.sleep(200); 194 } catch (InterruptedException e) { 195 e.printStackTrace(); 196 } 197 198 if (event == null) { 199 return; 200 } 201 202 // 连接状态 203 KeeperState keeperState = event.getState(); 204 // 事件类型 205 EventType eventType = event.getType(); 206 // 受影响的path 207 String path = event.getPath(); 208 //原子对象seq 记录进入process的次数 209 String logPrefix = "【Watcher-" + this.seq.incrementAndGet() + "】"; 210 211 System.out.println(logPrefix + "收到Watcher通知"); 212 System.out.println(logPrefix + "连接状态:\t" + keeperState.toString()); 213 System.out.println(logPrefix + "事件类型:\t" + eventType.toString()); 214 215 if (KeeperState.SyncConnected == keeperState) { 216 // 成功连接上ZK服务器 217 if (EventType.None == eventType) { 218 System.out.println(logPrefix + "成功连接上ZK服务器"); 219 connectedSemaphore.countDown(); 220 } 221 //创建节点 222 else if (EventType.NodeCreated == eventType) { 223 System.out.println(logPrefix + "节点创建"); 224 try { 225 Thread.sleep(100); 226 } catch (InterruptedException e) { 227 e.printStackTrace(); 228 } 229 } 230 //更新节点 231 else if (EventType.NodeDataChanged == eventType) { 232 System.out.println(logPrefix + "节点数据更新"); 233 try { 234 Thread.sleep(100); 235 } catch (InterruptedException e) { 236 e.printStackTrace(); 237 } 238 } 239 //更新子节点 240 else if (EventType.NodeChildrenChanged == eventType) { 241 System.out.println(logPrefix + "子节点变更"); 242 try { 243 Thread.sleep(3000); 244 } catch (InterruptedException e) { 245 e.printStackTrace(); 246 } 247 } 248 //删除节点 249 else if (EventType.NodeDeleted == eventType) { 250 System.out.println(logPrefix + "节点 " + path + " 被删除"); 251 } 252 else { 253 System.out.println(logPrefix + "其他事件:" + eventType); 254 }; 255 } 256 else if (KeeperState.Disconnected == keeperState) { 257 System.out.println(logPrefix + "与ZK服务器断开连接"); 258 } 259 else if (KeeperState.AuthFailed == keeperState) { 260 System.out.println(logPrefix + "权限检查失败"); 261 } 262 else if (KeeperState.Expired == keeperState) { 263 System.out.println(logPrefix + "会话失效"); 264 } 265 else { 266 System.out.println(logPrefix + "其他状态:" + keeperState); 267 }; 268 269 System.out.println("--------------------------------------------"); 270 271 } 272 273 /** 274 * <B>方法名称:</B>测试zookeeper监控<BR> 275 * <B>概要说明:</B>主要测试watch功能<BR> 276 * @param args 277 * @throws Exception 278 */ 279 public static void main(String[] args) throws Exception { 280 281 //建立watcher //当前客户端可以称为一个watcher 观察者角色 282 ZooKeeperWatcherYuCong zkWatch = new ZooKeeperWatcherYuCong(); 283 //创建连接 284 zkWatch.createConnection(CONNECTION_ADDR, SESSION_TIMEOUT); 285 System.out.println(zkWatch.zk.toString()); 286 287 Thread.sleep(1000); 288 289 // 清理节点 290 zkWatch.deleteAllTestPath(false); 291 292 //-----------------第一步: 创建父节点 /p ------------------------// 293 if (zkWatch.createPath(PARENT_PATH, System.currentTimeMillis() + "", false)) { 294 295 Thread.sleep(1000); 296 297 //-----------------第二步: 读取节点 /p 和 读取/p节点下的子节点(getChildren)的区别 --------------// 298 // 读取数据 299 zkWatch.readData(PARENT_PATH, false); 300 301 // 读取子节点(监控childNodeChange事件) 302 zkWatch.getChildren(PARENT_PATH, false); 303 304 // 更新数据 305 zkWatch.writeData(PARENT_PATH, System.currentTimeMillis() + ""); 306 Thread.sleep(1000); 307 308 // 创建子节点 309 zkWatch.createPath(CHILDREN_PATH, System.currentTimeMillis() + "", false); 310 311 312 //-----------------第三步: 建立子节点的触发 --------------// 313 // zkWatch.createPath(CHILDREN_PATH + "/c1", System.currentTimeMillis() + "", true); 314 // zkWatch.createPath(CHILDREN_PATH + "/c1/c2", System.currentTimeMillis() + "", true); 315 316 //-----------------第四步: 更新子节点数据的触发 --------------// 317 //在进行修改之前,我们需要watch一下这个节点: 318 Thread.sleep(1000); 319 zkWatch.readData(CHILDREN_PATH, true); 320 zkWatch.writeData(CHILDREN_PATH, System.currentTimeMillis() + ""); 321 322 } 323 324 Thread.sleep(10000); 325 // 清理节点 326 zkWatch.deleteAllTestPath(false); 327 328 329 Thread.sleep(10000); 330 zkWatch.releaseConnection(); 331 332 } 333 334 }