zookeeper主要是为了统一分布式系统中各个节点的工作状态,在资源冲突的情况下协调提供节点资源抢占,提供给每个节点了解整个集群所处状态的途径。这一切的实现都依赖于zookeeper中的事件监听和通知机制。

zookeeper中的事件和状态

事件和状态构成了zookeeper客户端连接描述的两个维度。注意,网上很多帖子都是在介绍zookeeper客户端连接的事件,但是忽略了zookeeper客户端状态的变化也是要进行监听和通知的。这里我们通过下面的两个表详细介绍zookeeper中的事件和状态(zookeeper API中被定义为@Deprecated的事件和状态就不介绍了)。

zookeeper客户端与zookeeper server连接的状态

连接状态 状态含义
KeeperState.Expired 客户端和服务器在ticktime的时间周期内,是要发送心跳通知的。这是租约协议的一个实现。客户端发送request,告诉服务器其上一个租约时间,服务器收到这个请求后,告诉客户端其下一个租约时间是哪个时间点。当客户端时间戳达到最后一个租约时间,而没有收到服务器发来的任何新租约时间,即认为自己下线(此后客户端会废弃这次连接,并试图重新建立连接)。这个过期状态就是Expired状态
KeeperState.Disconnected 就像上面那个状态所述,当客户端断开一个连接(可能是租约期满,也可能是客户端主动断开)这是客户端和服务器的连接就是Disconnected状态
KeeperState.SyncConnected 一旦客户端和服务器的某一个节点建立连接(注意,虽然集群有多个节点,但是客户端一次连接到一个节点就行了),并完成一次version、zxid的同步,这时的客户端和服务器的连接状态就是SyncConnected
KeeperState.AuthFailed zookeeper客户端进行连接认证失败时,发生该状态

需要说明的是,这些状态在触发时,所记录的事件类型都是:EventType.None。

zookeeper中的watch事件(当zookeeper客户端监听某个znode节点”/node-x”时)

zookeeper事件 事件含义
EventType.NodeCreated 当node-x这个节点被创建时,该事件被触发
EventType.NodeChildrenChanged 当node-x这个节点的直接子节点被创建、被删除、子节点数据发生变更时,该事件被触发。
EventType.NodeDataChanged 当node-x这个节点的数据发生变更时,该事件被触发
EventType.NodeDeleted 当node-x这个节点被删除时,该事件被触发。
EventType.None 当zookeeper客户端的连接状态发生变更时,即KeeperState.Expired、KeeperState.Disconnected、KeeperState.SyncConnected、KeeperState.AuthFailed状态切换时,描述的事件类型为EventType.None

watch机制

Znode发生变化(Znode本身的增加,删除,修改,以及子Znode的变化)可以通过Watch机制通知到客户端。那么要实现Watch,就必须实现org.apache.zookeeper.Watcher接口,并且将实现类的对象传入到可以Watch的方法中。Zookeeper中所有读操作(getData(),getChildren(),exists())都可以设置Watch选项。Watch事件具有one-time trigger(一次性触发)的特性,如果Watch监视的Znode有变化,那么就会通知设置该Watch的客户端。

在上述说道的所有读操作中,如果需要Watcher,我们可以自定义Watcher,如果是Boolean型变量,当为true时,则使用系统默认的Watcher,系统默认的Watcher是在Zookeeper的构造函数中定义的Watcher。参数中Watcher为空或者false,表示不启用Wather。

watch特性1:一次性触发器

客户端在Znode设置了Watch时,如果Znode内容发生改变,那么客户端就会获得Watch事件。例如:客户端设置getData("/znode1", true)后,如果/znode1发生改变或者删除,那么客户端就会得到一个/znode1的Watch事件,但是/znode1再次发生变化,那客户端是无法收到Watch事件的,除非客户端设置了新的Watch。

watch特性2:发送至客户端

Watch事件是异步发送到Client。Zookeeper可以保证客户端发送过去的更新顺序是有序的。例如:某个Znode没有设置watcher,那么客户端对这个Znode设置Watcher发送到集群之前,该客户端是感知不到该Znode任何的改变情况的。换个角度来解释:由于Watch有一次性触发的特点,所以在服务器端没有Watcher的情况下,Znode的任何变更就不会通知到客户端。不过,即使某个Znode设置了Watcher,且在Znode有变化的情况下通知到了客户端,但是在客户端接收到这个变化事件,但是还没有再次设置Watcher之前,如果其他客户端对该Znode做了修改,这种情况下,Znode第二次的变化客户端是无法收到通知的。这可能是由于网络延迟或者是其他因素导致,所以我们使用Zookeeper不能期望能够监控到节点每次的变化。Zookeeper只能保证最终的一致性,而无法保证强一致性。

watch特性3:设置watch的数据内容

Znode改变有很多种方式,例如:节点创建,节点删除,节点改变,子节点改变等等。Zookeeper维护了两个Watch列表,一个节点数据Watch列表,另一个是子节点Watch列表。getData()和exists()设置数据Watch,getChildren()设置子节点Watch。两者选其一,可以让我们根据不同的返回结果选择不同的Watch方式,getData()和exists()返回节点的内容,getChildren()返回子节点列表。因此,setData()触发内容Watch,create()触发当前节点的内容Watch或者是其父节点的子节点Watch。delete()同时触发父节点的子节点Watch和内容Watch,以及子节点的内容Watch。

Zookeeper Watcher的运行机制

1,Watch是轻量级的,其实就是本地JVM的Callback,服务器端只是存了是否有设置了Watcher的布尔类型。(源码见:org.apache.zookeeper.server.FinalRequestProcessor)
2,在服务端,在FinalRequestProcessor处理对应的Znode操作时,会根据客户端传递的watcher变量,添加到对应的ZKDatabase(org.apache.zookeeper.server.ZKDatabase)中进行持久化存储,同时将自己NIOServerCnxn做为一个Watcher callback,监听服务端事件变化
3,Leader通过投票通过了某次Znode变化的请求后,然后通知对应的Follower,Follower根据自己内存中的zkDataBase信息,发送notification信息给zookeeper客户端。
4,Zookeeper客户端接收到notification信息后,找到对应变化path的watcher列表,挨个进行触发回调。

流程图

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

zookeeper(四):核心原理(Watcher、事件和状态)的更多相关文章

  1. zookeeper(2) zookeeper的核心原理

    zookeeper 的前世今生 分布式系统的很多难题,都是由于缺少协调机制造成的.在分布式协调这块做得比较好的,有 Google 的 Chubby 以及 Apache 的 Zookeeper. Goo ...

  2. Zookeeper核心原理

    Zookeeper 的核心原理 Zookeeper 的由来 各个节点的数据一致性 怎么保证任务只在一个节点执行 如果orderserver1挂了,其他节点如何发现并接替 存在共享资源,互斥性.安全性 ...

  3. Zookeeper学习之Watcher事件类型和ZK状态

    1.Zookeepe  Watcherr的事件类型和ZK状态. zookeeper:Watcher.ZK状态,事件类型(一)zookeeper有watch事件,是一次性触发的,当watch监视的数据发 ...

  4. hadoop系列:zookeeper(2)——zookeeper核心原理(选举)

    1.前述 上篇文章<hadoop系列:zookeeper(1)--zookeeper单点和集群安装>(http://blog.csdn.net/yinwenjie/article/deta ...

  5. Zookeeper系列2 原生API 以及核心特性watcher

    原生API 增删改查询 public class ZkBaseTest { static final String CONNECT_ADDR = "192.168.0.120"; ...

  6. zookeeper核心原理全面解析

    下述各zookeeper机制的java客户端实践参考zookeeper java客户端之curator详解. 官方文档http://zookeeper.apache.org/doc/current/z ...

  7. 《从Paxos到ZooKeeper 分布式一致性原理与实践》阅读【Watcher】

    ZooKeeper 允许客户端向服务端注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能. ZooKeeper ...

  8. 抽丝剥茧分析asyncio事件调度的核心原理

    先来看一下一个简单的例子 例1: async def foo(): print('enter foo ...') await bar() print('exit foo ...') async def ...

  9. 深入了解Zookeeper核心原理

    之前的文章Zookeeper基础原理&应用场景详解中将Zookeeper的基本原理及其应用场景做了一个详细的介绍,虽然介绍了其底层的存储原理.如何使用Zookeeper来实现分布式锁.但是我认 ...

随机推荐

  1. CentOS 6.9/Ubuntu 16.04源码安装RabbitMQ(二进制包tar.gz)

    说明:CentOS的安装方式同样适合在Ubuntu中,把源改成APT即可. 一.安装erlang: 下载erlang: 从Erlang的官网http://www.erlang.org/download ...

  2. HDU 1864 Brave Game 【组合游戏,SG函数】

    简单取石子游戏,SG函数的简单应用. 有时间将Nim和.SG函数总结一下……暂且搁置. #include <cstdio> #include <cstring> #define ...

  3. Linux(CentOS)下的vsftpd服务器配置-五岳之巅

    说明:VSFTPD这款软件,网上和书里有很多配置文章,但不一定适用于您的主机,不同版本默认值不一样,我现在使用的是vsftpd-2.0.5-12.el5_3.1.千万记住:修改配置文件后,必须重新启动 ...

  4. 【java】java获取对象属性类型、属性名称、属性值

    java获取对象属性类型.属性名称.属性值 获取属性 修饰符:[在Field[]循环中使用] String modifier = Modifier.toString(fields[i].getModi ...

  5. 【POI】解析xls报错:java.util.zip.ZipException: error in opening zip file

    今天使用POI解析XLS,报错如下: Servlet.service() for servlet [rest] in context with path [/cetBrand] threw excep ...

  6. SqlServer查看对象(表、存储过程、函数)在哪些地方被引用或引用哪些地方

    对象(如表)-->右键-->查看依赖关系 依赖于[]的对象:列出哪些存储过程.函数引用了该表 []依赖的对象:列出该存储过程.函数依赖了哪些表.函数.存储过程

  7. IIS_未能映射路径“/”。

      解决方法: 使用托管管道默认是为[经典]的应用程序池

  8. 解决kylin报错:java.lang.IllegalStateException

    一个kylin build job执行到第三步Extract Fact Table Distinct Columns时报错: 2017-05-24 20:04:07,930 ERROR [pool-9 ...

  9. Pod中访问外部的域名配置

    在实际应用中经常遇到Pod中访问外部域名的状况,在Kubenetes 1.6以上的版本通过配置DNS configmap已经解决,详细的内容可以参考官方的 https://kubernetes.io/ ...

  10. 用CRF++开源工具做文本序列标注教程

    本文只介绍如何快速的使用CRF++做序列标注,对其中的原理和训练测试参数不做介绍. 官网地址:CRF++: Yet Another CRF toolkit 主要完成如下功能: 输入 -> &qu ...