ZK会话
ZK会话有四种状态:NOT_CONNECTED, CONNECTING, CONNECTED, CLOSED, 会话状态转换图为
- Client初始化连接,状态转为CONNECTING(①)
- Client与Server成功建立连接,状态转为CONNECTED(②)
- Client丢失了与Server的连接或者没有接受到Server的响应,状态转为CONNECTING(③)
- Client连上另外的Server或连接上了之前的Server,状态转为CONNECTED(②)
- 若会话过期(是Server负责声明会话过期,而不是Client ),状态转为CLOSED(⑤),状态转为CLOSED
- Client也可以主动关闭会话(④),状态转为CLOSED
为了创建会话,ZK应用需要提供一个或一组ZK服务器地址,以逗号分隔(如:127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184,127.0.0.1:2185). ZK客户端会随意挑选一个ZK服务器并与之连接。倘若连接失败,或者连接成功后连接丢失。ZK客户端会再挑选另一个ZK服务器连接。 每个会话建立后,ZK服务器都会分配一个64位的标识ID,在Client创建会话时,可以指定超时时间T,若Server在T内没有收到客户端任何消息,将认为会话过期。 而对于Client,若在在T/3时没有收到Server的响应,将发送一个心跳信息给Server, 在2/3T时刻还是没有收到Server的响应,则会尝试重连其他Server(这样就还有T/3时间查找另外的Server)。 在Client重连不同的Server时,该Server的状态必须同该Client观察到的Server状态一致(或者更新),这一点很重要。 Client不能连接上状态过时的Server(没有看到该Client已经看到的服务器状态变化)。这时就用到了之前所述的Zxid,这样就能保证Client不会重连上比自己过时的Server,如图:
ZK watches
在ZK中所有的读操作getData(),getChildren()和exist(),可以选择建立一个watch。watch是一个一次性的,当客户端watch的数据发生变化时,会通知到客户端。客户端通过注册来接收节点变化的通知。注册接收通知通过Watch 来设置。而Watch是一次性操作,也就是只能接收一次通知,如果需要继续得到通知, 则需要再次Watch。
由于通知是一次性操作,因此某些情况会有一些问题,如 1. 客户端c1对节点/tasks设置了Watch 2. 客户端c2在节点/tasks添加了新节点 3. 客户端c1收到了通知 4. 客户端c1设置新的Watch, 但在设置之前, 客户端c3在节点/tasks添加了新的节点 5. 此时客户端c1就丢失了来自客户端c3的更新通知 上述的问题,我们可以在客户端c1设置完Watch后,读取一次/tasks的状态,这样就能防止丢失更新通知。 尽管这样可能导致Zookeeper的状态改变分发变慢,但是更重要的是, 这样使得所有客户端可以统一顺序感知到Zookeeper的状态改变,这是十分关键的。 Zookeeper支持多种通知,这要看客户端Watch什么样的事件,如 节点数据改变,子节点改变, 节点删除,节点创建等, Zookeeper提供了Watcher对象来调用对应API, 后面会详述该对象。 下面是ZK watch我们需要注意的关键概念:
(一次性触发)One-time trigger
当设置监视的数据发生改变时,该监视事件会被发送到客户端,例如,如果客户端调用了 getData(“/znode1”, true) 并且稍后 /znode1 节点上的数据发生了改变或者被删除了,客户端将会获取到 /znode1 发生变化的监视事件,而如果 /znode1 再一次发生了变化,除非客户端再次对 /znode1 设置监视,否则客户端不会收到事件通知。
(发送至客户端)Sent to the client
Zookeeper 客户端和服务端是通过 socket 进行通信的,由于网络存在故障,所以监视事件很有可能不会成功地到达客户端,监视事件是异步发送至监视者的,Zookeeper 本身提供了保序性(ordering guarantee):即客户端只有首先看到了监视事件后,才会感知到它所设置监视的 znode 发生了变化(a client will never see a change for which it has set a watch until it first sees the watch event). 网络延迟或者其他因素可能导致不同的客户端在不同的时刻感知某一监视事件,但是不同的客户端所看到的一切具有一致的顺序。
(被设置 watch 的数据)The data for which the watch was set
这意味着 znode 节点本身具有不同的改变方式。你也可以想象 Zookeeper 维护了两条监视链表:数据监视和子节点监视(data watches and child watches) getData() and exists() 设置数据监视,getChildren() 设置子节点监视。 或者,你也可以想象 Zookeeper 设置的不同监视返回不同的数据,getData() 和 exists() 返回 znode 节点的相关信息,而 getChildren() 返回子节点列表。因此, setData() 会触发设置在某一节点上所设置的数据监视(假定数据设置成功),而一次成功的 create() 操作则会出发当前节点上所设置的数据监视以及父节点的子节点监视。一次成功的 delete() 操作将会触发当前节点的数据监视和子节点监视事件,同时也会触发该节点父节点的child watch。
Zookeeper 中的监视是轻量级的,因此容易设置、维护和分发。当客户端与 Zookeeper 服务器端失去联系时,客户端并不会收到监视事件的通知,只有当客户端重新连接后,若在必要的情况下,以前注册的监视会重新被注册并触发,对于开发人员来说 这通常是透明的。只有一种情况会导致监视事件的丢失,即:通过 exists() 设置了某个 znode 节点的监视,但是如果某个客户端在此 znode 节点被创建和删除的时间间隔内与 zookeeper 服务器失去了联系,该客户端即使稍后重新连接 zookeeper服务器后也得不到事件通知。