数据模型
- ZK通过DataTree来管理全量内存数据,DataTree是一种树型结构,它的每个节点(称之为ZNode)都是DataNode类型,并且和唯一的路径相对应。
- DataTree底层其实主要是一个ConcurrentHashMap,key为节点路径,value为对应的DataNode。
- 每个ZNode内部记录了当前结点的数据、ACL列表、节点状态stat、以及父结点、子节点列表。
- ZK结点有四种类型:①持久非顺序型:一旦被创建就一直存在,直到客户端来主动删除;②持久顺序型:相比于持久非顺序型,增加了顺序性,每个父节点都会为其第一级子结点维护一份顺序,然后在节点创建的时候给节点名后面添加上序号,形成新的完整的节点名;③临时非顺序型:临时结点会在创建该节点的客户端会话失效后被删除;④临时顺序型;
ACL权限管理
ACL主要通过三个部分来完成权限管理:权限模式、授权对象、授权用户对该结点拥有的权限,可以通过这三个元素来标识一个有效的ACL权限信息。
常用权限模式:
- IP模式:通过IP地址粒度来进行权限控制,如"ip:192.168.0.110"表示权限控制针对该IP地址;
- Digest模式:使用"username:password"形式的权限标识来进行权限配置,注意ZK会对密码进行SHA-1加密和BASE64编码;
- World模式:对所有用户开放权限,任何用户都可以在不进行权限校验的情况下操作ZK数据;
- Super模式:是一种特殊的Digest模式,该模式下只有超级用户可以对ZK数据进行任何操作;
授权对象:拥有该权限的主体,如IP地址等,不同的权限模式通常有不同的授权对象。
权限类型:CREATE(节点创建权限)、DELETE(节点删除权限)、READ(节点读取权限)、WRITE(节点更新权限)、ADMIN(对节点进行ACL设置的权限)。
Watcher(事件监听)机制
ZK允许客户端向服务端注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,那么服务端就会向指定客户端发送一个事件通知。具体来说有客户端注册Watcher、服务端处理Watcher、客户端回调Watcher3个过程。
客户端注册Watcher
- 客户端传入Watcher接口的实现对象并向服务端发送请求;
- 客户端标记该请求request,然后将Watcher包装到WatchRegistration中,用于保存节点路径和Watcher的对应关系;
- 客户端将这个包含WatchRegistration地请求放入发送队列中,然后由SendThread线程将其发送给服务端,并等待服务端的响应;
- 收到响应后,客户端会从封装对象中提取出Watcher并将其注册到ZKWatcherManager的dataWatches中去,该结构为Map<String, Set>,key为结点路径,value为该节点上的Watcher;
服务端处理Watcher
- 当服务端接收到请求后会判断该请求是否需要注册Watcher,如果该请求被标记为需要注册,则会将该节点的路径、该连接对应的ServerCnxn注册到服务端的WatcherManager当中。ServerCnxn代表了一个客户端和服务端的连接,它实现了Watcher接口的process方法,可以将其看作一个Watcher。
- 当发生对应的操作(如setData)时,会在该操作结束以后通过调用WatcherManager的方法来触发对应节点上的对应类型的事件;
- WatcherManager会从自己管理的Watcher集合中找到对应的Watcher,然后将其从集合中删除;
- 然后调用该Watcher也就是ServerCnxn的process方法,该方法会向客户端发送一个事件通知;
客户端回调Watcher
- 当客户端的SendThread线程接收到消息后,如果该消息的类型为事件通知,就会根据节点路径和事件类型从ZKWatcherManager中获取对应的Watcher并删除;
- 将获取到的Watcher添加到EventThread的一个阻塞队列当中,EventThread线程的run方法就是不断地从该队列中获取Watcher并执行它们的process方法,完成事件监听的回调;
注意:
- 客户端和服务端之间用于传递事件通知的WatcherEvent只包含了事件类型、结点路径,客户端无法从中获取到结点的内容,所以需要客户端主动去服务端拉取结点数据;
- Watcher是一次性的,一旦被触发就会被删除掉,所以一个Watcher至多会被触发和回调一次;
- 客户端回调Watcher是一个串行同步的过程,所以最好不要在Watcher回调中执行耗时操作,避免阻塞后续回调;