【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的监听机制实现的发布订阅系统使用的"推拉"结合的方式。

相关推荐