【zookeeper源码阅读】数据模型Znode
本文主要对zookeeper的数据模型Znode进行的简要说明
主要内容:
1. zk的主要应用场景
2. zk的数据节点的概述
- 数据节点的类型
- 节点的数据结构
- 节点上的监听机制
1. zk的主要应用场景
- 数据的发布与订阅
- 负载均衡
- 统一命名服务
- 分布式协调与通知
- 集群管理
- master选举
- 分布式锁与分布式队列
具体将在后续的文章中给出具体的应用介绍。
2. zk的数据节点的概述
在介绍数据节点之前,先给大家说明一下zk中事务的概念。zk中的事务指的是能够改变zk服务器状态的操作,包括
- 数据节点的删除,创建和内容更新等
- 客户端会话的创建与失效
zk中还有一个事务id的概念,会为每次事务请求分配一个全局唯一的id,关于事务id的特点简要说明如下:
- 64位数字
- 高32是Leader统治时期的标识,说明该事务是在哪个Leader时期进行的
- 低32位是事务的id号
- 通过这64位数字可以确定一个事务请求的全局唯一的顺序
2.1 zk数据节点的类型
zk中的数据节点是有生命周期的概念的,根据生命周期的不同可以划分为两种类型的节点
- 持久节点(Persistent)
最常见的一种节点类型,也叫做regular节点。数据节点一旦被创建,就会一直存在于zk服务器上,直到有删除事务操作主动清除该节点
- 临时节点(Ephemeral)
临时节点的生命周期是和客户端会话绑定在一起的,只要客户端会话失效,那么临时节点就会被清除。这里的客户端会话失效和客户端tcp连接断开是不一样的。
临时节点只能是leaf节点
- 顺序节点(Sequential)
zk会给创建的节点自动加上一个数字后缀,作为一个完整的节点名称,这种方式可以维护节点之间的一种顺序性
- 持久节点+顺序节点
- 临时节点+顺序节点
2.2 节点的数据结构
数据节点的类结构代码如下:
public class DataNode implements Record { byte data[]; //节点上存储的数据 Long acl; //权限控制列表版本号 public StatPersisted stat; //节点状态信息 private Set<String> children = null; //节点的子节点列表 }
StatPersisted stat是该节点的状态信息类,类结构代码如下:
public class StatPersisted implements Record { private long czxid; //创建该节点的事务id private long mzxid; //最后一次修改节点的事务id private long ctime; //节点的创建时间 private long mtime; //最后一次更新节点内容的时间 private int version; //数据节点的版本号,version=0表示该节点被创建之后,更新过0次,在实现乐观锁的时候有用 private int cversion; //子节点的版本号,只有当子节点列表被更改后,才会+1 private int aversion; //子节点的acl列表版本号 private long ephemeralOwner; //如果是临时节点,该值表示创建该节点的临时会话的sessionID,如果是持久节点的话,该值为0 private long pzxid; //子节点列表最后一次被修改时的事务id,一定是子节点列表被修改,子节点的内容被修改是不会改变这个属性的值的 }
2.3 节点上的监听机制
- zookeeper允许客户端对象在数据节点上注册事件监听,可以实现数据的发布与订阅。
- 当zk服务端的一些操作触发了事件监听Watcher,就会向指定的客户端发送事件通知。
zk中的事件监听机制由三部分组成:
- zk服务端
- 客户端线程
- 客户端的WatcherManager
客户端线程在zk服务器上注册事件监听之后,会将这个watcher对象存储在WatcherManager中,当zk服务器触发这个事件之后,会向客户端发送通知,客户端线程会从WatcherManager中取出对应的Watcher对象来执行回调逻辑。
监听器的接口类Watcher代码如下
public interface Watcher{ public interface Event{ public enum KeeperState{ Disconnected(0), //断开连接 SyncConnected(3), //此时客户端与zk服务器处于连接状态 AuthFailed(4), //权限检查失败 ConnectedReadOnly(5) //客户端和一台只读server处于连接状态,what is readonly server? 就是只和集群中的一小部分服务器处于连接的服务器 Expired(-122) //会话超时 } public enum EventType{ None (-1), NodeCreated (1), //watcher监听的数据节点被创建 NodeDeleted (2), //watcher监听的数据节点被删除 NodeDataChanged (3), //watcher监听的数据节点内容被更新 NodeChildrenChanged (4), //watcher监听的数据节点的子节点列表被更新了 DataWatchRemoved (5), ChildWatchRemoved (6); } } public enum WatcherType{ Children (1), Data (2), Any (3); } abstract public void process(WatchedEvent event); }
主要有以下几个部分组成
- Watcher.Event.KeeperState: 表示通知状态
- Watcher.Event.EventType: 表示通知的事件类型
- Watcher.WatcherType: 监听器的类型
- process(WatchedEvent event) 事件的回调函数
在处理监听事件的回调函数process中,参数event是一个org.apache.zookeeper.WatchedEvent类型的参数。WatchedEvent说明如下
public class WatchedEvent { final private KeeperState keeperState; //通知的状态 final private EventType eventType; //事件类型 private String path; //参与该事件的节点路径 /** *实现WatchedEvent到WatcherEvent的转换 */ public WatcherEvent getWrapper() { return new WatcherEvent(eventType.getIntValue(), keeperState.getIntValue(), path); } /** *实现WatcherEvent到WatchedEvent的转换 */ public WatchedEvent(WatcherEvent eventMessage) { keeperState = KeeperState.fromInt(eventMessage.getState()); eventType = EventType.fromInt(eventMessage.getType()); path = eventMessage.getPath(); } }
从WatchedEvent的代码中可以看出一个监听事件由三个部分组成的:
- Watcher.Event.KeeperState:通知状态
- Watcher.Event.EventType:通知类型
- String path:参与该事件的节点路径
并且,与WatchedEvent对应的还有一个类WatcherEvent,这个WatcherEvent和WatchedEvent相比,只是实现了序列化,方便通过网络在服务端和客户端之间传输。
序列化使用的是jute
整个监听通知的机制过程描述如下:
- 客户端在服务器数据节点上注册相应的监听事件Watcher,注册的方式后面阐述
- 服务端操作触发该事件,生成WatchedEvent事件对象
- 调用getWrapper方法将WatchedEvent包装称为一个实现序列化的、可在网络上传输的WatcherEvent对象
- 通过网络将WatcherEvent对象发送到client
- client将WatcherEvent对象还原成为一个WatchedEvent对象,并传递给process方法进行处理
客户端接受到的服务器传送过来的监听事件的格式如下:
KeeperState:SyncConnected EventType:NodeDataChanged Path: /zk-book
显然,从接收到的事件中,客户端是无法获取到该节点在更新前后的数据的,因此客户端是要主动再向服务器获取数据。因此,使用zk的监听机制实现的发布订阅系统使用的"推拉"结合的方式。
相关推荐
ZooKeeper支持某些特定的四字命令字母与其的交互。它们大多是查询命令,用来获取ZooKeeper服务的当前状态及相关信息。用户在客户端可以通过telnet或nc向ZooKeeper提交相应的命令