分布式学习手记07 - ZooKeeper 核心技术(上)

2 分钟读完

ZooKeeper 的核心技术细节第一部分,包括:系统模型、序列化与协议、客户端工作原理、会话。

系统模型

数据模型、节点特性、版本、Watcher 和 ACL

数据模型

  • ZNode

    ZNode 是 ZooKeeper 中最小数据单元,可以保存数据或挂载子节点

  • 多个 ZNode 按层次化结构进行组织,形成一棵树

  • 事务ID

    每一个事务会分配一个全局唯一的事务ID(ZXID),共 64 位,包含事务标识和事务顺序两部分

节点特性

  • 类型

    1. 持久节点

    2. 持久顺序节点

    3. 临时节点 - 随客户端会话失效而销毁;不能基于临时节点创建子节点

    4. 临时顺序节点

  • 状态信息

    ZNode 上存储数据内容外,还存储数据节点的状态信息

[zk: localhost:2181(CONNECTED) 3] get /zk-book
123
cZxid = 0x200000004
ctime = Tue Jun 26 11:26:02 CST 2018
mZxid = 0x200000004
mtime = Tue Jun 26 11:26:02 CST 2018
pZxid = 0x200000004
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0

版本

ZooKeeper 中的版本表示的是对数据节点的数据内容、子节点列表,或是节点 ACL 信息的修改次数。

实现类似于乐观锁机制中的 “写入校验” ,保证分布式数据操作的原子性。

  • version - 当前数据节点数据内容的版本号

  • cversion - 当前数据节点子节点的版本号

  • aversion - 当前数据节点 ACL 变更版本号

Watcher

实现分布式系统中的变更通知功能

  • 流程机制

    1. 客户端向 ZooKeeper 服务器注册 Watcher,同时将 Watcher 对象存储在客户端的 WatchManager 中;

    2. ZooKeeper 服务端触发 Watcher 事件后,向客户端发送通知;

    3. 客户端线程从 WatchManager 中取出对应的 Watcher 对象来执行回调逻辑。

  • 特性

    1. 一次性

      Watcher 一旦触发使用完,移除销毁

    2. 客户端串行执行

      客户端 Watcher 回调串行同步,保证顺序

    3. 轻量

      ZooKeeper 服务端发送的 Watcher 通知仅告诉客户端发生了事件,不含事件具体内容;

      客户端注册 Watcher 时,不会上传真实的 Watcher 对象,而是存储在客户端的 WatchManager 里

ACL

ZooKeeper 提供一套完善的 ACL(Access Control List)权限控制机制来保障数据的安全

更细粒度的权限管理方式 ‘scheme : id : permission’

  • 权限模式 Scheme

    权限验证过程中使用的验证策略: IP、Digest、World、Super

  • 授权对象 ID

    权限赋予的用户或一个指定实体

  • 权限 Permission

    通过权限检查后可以被允许执行的操作: CREATE、 DELETE、READ、WRITE、ADMIN

序列化与协议

ZooKeeper 客户端和服务端之间会进行一系列网络通信以实现数据的传输。对于网络通信,首要解决对数据的序列化和反序列化处理。

Jute

Jute 是 ZooKeeper 的序列化组件

  • Record 接口

    1. 序列化方法: serialize

    2. 反序列化方法: deserialize

  • 序列化器

    1. 序列化器: OutputArchive

    2. 反序列化器: InputArchive

  • Jute 配置文件

    zookeeper.jute

通信协议

ZooKeeper 基于 TCP/IP 实现了自己的通信协议

  • 请求

    1. 请求头 RequestHeader (xid & type)

    2. 请求体 Request (请求的所有操作内容)

  • 响应

    1. 响应头 ReplyHeader (xid & zxid & err)

    2. 响应体 Response (响应的所有返回数据)

客户端

客户端主要组件

  • ZooKeeper 实例: 客户端的入口

  • ClientWatchManager: 客户端 Watcher 管理器

  • HostProvider: 客户端地址列表管理器

  • ClientCnxn: 客户端核心线程

一次会话的创建过程

初始化阶段

  1. 初始化 ZooKeeper 对象

    实例化一个 ZooKeeper 对象;

    创建一个客户端的 Watcher 管理器:ClientWatchManager

  2. 设置会话默认 Watcher

    将构造方法中传入的 Watcher 对象存入 ClientWatchManager

  3. 构造服务器地址列表管理器: HostProvider

    将构造方法中传入的服务器地址,存入 HostProvider

  4. 创建并初始化客户端网络连接器: ClientCnxn

    客户端创建一个网络连接器 ClientCnxn 来管理客户端和服务器的网络交互;

    创建客户端的请求发送队列 outgoingQueue,服务端响应的等待队列 pendingQueue;

    创建底层 I/O 处理器: ClientCnxnSocket

  5. 初始化核心网络线程 SendThread 和 EventThread

    • SendThread: 管理客户端和服务器之间所有网络 I/O

      使用 ClientCnxnSocket 作为底层网络 I/O 处理器

    • EventThread: 客户端的事件处理

      初始化待处理事件队列 waitingEvents 用于存放所有等待被客户端处理的事件

会话创建阶段

  1. 启动 SendThread 和 EventThread

  2. 获取服务器地址

    SendThread 从 HostProvider 中随机获取一个 ZooKeeper 服务器地址

  3. 创建 TCP 连接

    ClientCnxnSocket 负责和服务器创建一个 TCP 长连接

  4. 构造 ConnectRequest 请求

    SendThread 根据当前客户端实际设置,构造出 ConnectRequest 请求,即表示客户端试图与服务器创建一个会话;

    客户端进一步将该请求包装成网络 I/O 层的 Packet 对象,放入请求发送队列 outgoingQueue

  5. 发送请求

    ClientCnxnSocket 负责从 outgoingQueue 中取出一个待发送的 Packet 对象,将其序列化成 ByteBuffer 后,向服务端发送

响应处理阶段

  1. 接收服务端响应

    ClientCnxnSocket 对接收到的服务端响应进行判断

  2. 处理 Response

    ClientCnxnSocket 会对接收到的服务端响应反序列化,得到 ConnectResponse 对象,并从中获取到 ZooKeeper 服务端分配的会话 sessionId

  3. 连接成功

    通知 SendThread 线程,进一步对客户端进行会话参数的设置,并更新客户端状态;

    通知 HostProvider 当前成功连接的服务器地址

  4. 生成事件: SyncConnected-None

    SendThread 生成一个事件 SyncConnected-None,代表客户端和服务器会话创建成功,并将该事件传递给 EventThread,从而让上层应用感知到会话的成功创建

  5. 查询 Watcher

    EventThread 线程收到事件后,会从 ClientWatchManager 中查询对应的 Watcher,将其放到 EventThread 的 waitingEvents 队列中去

  6. 处理事件

    EventThread 不断从 waitingEvents 队列中取出待处理的 Watcher 对象,对其触发处理

服务器地址列表

ZooKeeper 服务器地址列表即 connectString 参数,形如

ip1:2181,ip2:2181,ip3:2181

解析器处理:解析 chrootPath;保存服务器地址列表

  • 客户端隔离命名空间:Chroot

    客户端自定义一个 Namespace,该客户端对服务器的所有操作都限制在此 Namespace 内,即对应到服务端一颗子树上,从而实现应用间隔离

    设置 Chroot

    ip1:2181,ip2:2181,ip3:2181/apps/X
    
  • 地址列表管理器:HostProvider

ClientCnxn

负责维护客户端与服务端之间的网络连接并进行一系列网络通信

  • Packet

    一个对协议层的封装,作为 ZooKeeper 中请求与响应的载体

    outgoingQueue:客户端的请求发送队列,存储需要发送到服务端的 Packet 集合

    pendingQueue: 服务端响应的等待队列,存储已从客户端发送到服务端,需要等待服务端响应的 Packet 集合

  • ClientCnxnSocket:底层 Socket 通信层

    • 请求发送

      从 outgoingQueue 队列提取一个可发送的 Packet 对象,生成一个客户端请求序号 XID 加入 Packet 请求头,将其序列化后发送;

      请求发送完毕后,将该 Packet 保存到 pendingQueue 队列中,等待服务端响应

    • 响应接收

      客户端获取到服务端响应后,根据请求类型不同,执行相应处理

  • SendThread:核心 I/O 调度线程

    维护客户端与服务端之间的会话生命周期;

    管理客户端所有请求发送和响应接收操作;

    将来自服务端的事件传递给 EventThread 去处理

  • EventThread

    负责客户端的事件处理,触发客户端注册的 Watcher 监听

会话

ZooKeeper 的连接与会话就是客户端通过实例化 ZooKeeper 对象来实现客户端与服务器创建并保持 TCP 连接的过程

会话状态

  • CONNECTING

  • CONNECTED

  • RECONNECTING

  • RECONNECTED

  • CLOSE

会话创建

  • Session

    客户端会话实体

  • SessionTracker

    服务端会话管理器,负责会话的创建、管理和清理等

  • 处理流程

    1. 处理 ConnectRequest 请求

    2. 会话创建

    3. 处理器链路处理

    4. 会话响应

会话管理

  • 分桶策略

    将每个会话的 “下次超时时间点” (ExprationTime)落在时间横轴上,划为多个区块,以区块(桶)为单位集中处理

  • 会话激活

    客户端在会话超时时间过期范围内向服务端发送心跳检测;服务端接收心跳检测,重新激活对应的客户端对话,即 TouchSession

    1. 检测会话状态

      Leader 检查会话是否关闭,若关闭则不再激活

    2. 计算会话新超时时间

      若会话未关闭,计算该会话下一次超时时间点 ExpirationTime_New

    3. 定位会话当前区块

      根据该会话当前超时时间 ExpirationTime_Old 定位其所在区块

    4. 迁移会话

      将该会话从老区块取出,放入 ExpirationTime_New 对应的新区块

  • 会话超时检查

    SessionTracker 按区块(桶)的时间点批量检查同一区块内的过期会话,提高效率和性能

会话清理

  1. 标记会话状态为 “已关闭”

    防止清理期间处理客户端新请求

  2. 发起 “会话关闭” 请求

    为了使会话关闭操作在集群内都生效,ZooKeeper 提交会话关闭请求

  3. 收集待清理的临时节点

    某个会话失效后,和该会话相关的临时节点需一并清除掉

    ZooKeeper 根据该会话 sessionID 获取对应临时节点列表

  4. 添加 “节点删除” 事务变更

    完成相关临时节点收集后,将这些临时节点转换成 “节点删除” 请求,放入事务变更队列

  5. 删除临时节点

  6. 移除会话

    节点删除后,移除 SessionTracker 中的会话信息

  7. 关闭 NIOServerCnxn

重连

客户端和服务端网络断开时,ZooKeeper 客户端会自动反复尝试重连

  • 连接断开: CONNECTION_LOSS

    网络闪断或当前连接的服务器故障,客户端会从地址列表内选取新地址尝试重连

  • 会话失效: SESSION_EXPIRED

    重连期间耗时过长,超过会话超时时间,服务端将该会话清理

  • 会话转移: SESSION_MOVED

    客户端重连后,可能是新的服务端,服务端会检查会话所有者(Owner),防止出现会话冲突

分类:

更新时间: