分布式学习手记07 - ZooKeeper 核心技术(上)
ZooKeeper 的核心技术细节第一部分,包括:系统模型、序列化与协议、客户端工作原理、会话。
系统模型
数据模型、节点特性、版本、Watcher 和 ACL
数据模型
-
ZNode
ZNode 是 ZooKeeper 中最小数据单元,可以保存数据或挂载子节点
-
树
多个 ZNode 按层次化结构进行组织,形成一棵树
-
事务ID
每一个事务会分配一个全局唯一的事务ID(ZXID),共 64 位,包含事务标识和事务顺序两部分
节点特性
-
类型
-
持久节点
-
持久顺序节点
-
临时节点 - 随客户端会话失效而销毁;不能基于临时节点创建子节点
-
临时顺序节点
-
-
状态信息
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
实现分布式系统中的变更通知功能
-
流程机制
-
客户端向 ZooKeeper 服务器注册 Watcher,同时将 Watcher 对象存储在客户端的 WatchManager 中;
-
ZooKeeper 服务端触发 Watcher 事件后,向客户端发送通知;
-
客户端线程从 WatchManager 中取出对应的 Watcher 对象来执行回调逻辑。
-
-
特性
-
一次性
Watcher 一旦触发使用完,移除销毁
-
客户端串行执行
客户端 Watcher 回调串行同步,保证顺序
-
轻量
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 接口
-
序列化方法: serialize
-
反序列化方法: deserialize
-
-
序列化器
-
序列化器: OutputArchive
-
反序列化器: InputArchive
-
-
Jute 配置文件
zookeeper.jute
通信协议
ZooKeeper 基于 TCP/IP 实现了自己的通信协议
-
请求
-
请求头 RequestHeader (xid & type)
-
请求体 Request (请求的所有操作内容)
-
-
响应
-
响应头 ReplyHeader (xid & zxid & err)
-
响应体 Response (响应的所有返回数据)
-
客户端
客户端主要组件
-
ZooKeeper 实例: 客户端的入口
-
ClientWatchManager: 客户端 Watcher 管理器
-
HostProvider: 客户端地址列表管理器
-
ClientCnxn: 客户端核心线程
一次会话的创建过程
初始化阶段
-
初始化 ZooKeeper 对象
实例化一个 ZooKeeper 对象;
创建一个客户端的 Watcher 管理器:ClientWatchManager
-
设置会话默认 Watcher
将构造方法中传入的 Watcher 对象存入 ClientWatchManager
-
构造服务器地址列表管理器: HostProvider
将构造方法中传入的服务器地址,存入 HostProvider
-
创建并初始化客户端网络连接器: ClientCnxn
客户端创建一个网络连接器 ClientCnxn 来管理客户端和服务器的网络交互;
创建客户端的请求发送队列 outgoingQueue,服务端响应的等待队列 pendingQueue;
创建底层 I/O 处理器: ClientCnxnSocket
-
初始化核心网络线程 SendThread 和 EventThread
-
SendThread: 管理客户端和服务器之间所有网络 I/O
使用 ClientCnxnSocket 作为底层网络 I/O 处理器
-
EventThread: 客户端的事件处理
初始化待处理事件队列 waitingEvents 用于存放所有等待被客户端处理的事件
-
会话创建阶段
-
启动 SendThread 和 EventThread
-
获取服务器地址
SendThread 从 HostProvider 中随机获取一个 ZooKeeper 服务器地址
-
创建 TCP 连接
ClientCnxnSocket 负责和服务器创建一个 TCP 长连接
-
构造 ConnectRequest 请求
SendThread 根据当前客户端实际设置,构造出 ConnectRequest 请求,即表示客户端试图与服务器创建一个会话;
客户端进一步将该请求包装成网络 I/O 层的 Packet 对象,放入请求发送队列 outgoingQueue
-
发送请求
ClientCnxnSocket 负责从 outgoingQueue 中取出一个待发送的 Packet 对象,将其序列化成 ByteBuffer 后,向服务端发送
响应处理阶段
-
接收服务端响应
ClientCnxnSocket 对接收到的服务端响应进行判断
-
处理 Response
ClientCnxnSocket 会对接收到的服务端响应反序列化,得到 ConnectResponse 对象,并从中获取到 ZooKeeper 服务端分配的会话 sessionId
-
连接成功
通知 SendThread 线程,进一步对客户端进行会话参数的设置,并更新客户端状态;
通知 HostProvider 当前成功连接的服务器地址
-
生成事件: SyncConnected-None
SendThread 生成一个事件 SyncConnected-None,代表客户端和服务器会话创建成功,并将该事件传递给 EventThread,从而让上层应用感知到会话的成功创建
-
查询 Watcher
EventThread 线程收到事件后,会从 ClientWatchManager 中查询对应的 Watcher,将其放到 EventThread 的 waitingEvents 队列中去
-
处理事件
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
服务端会话管理器,负责会话的创建、管理和清理等
-
处理流程
-
处理 ConnectRequest 请求
-
会话创建
-
处理器链路处理
-
会话响应
-
会话管理
-
分桶策略
将每个会话的 “下次超时时间点” (ExprationTime)落在时间横轴上,划为多个区块,以区块(桶)为单位集中处理
-
会话激活
客户端在会话超时时间过期范围内向服务端发送心跳检测;服务端接收心跳检测,重新激活对应的客户端对话,即 TouchSession
-
检测会话状态
Leader 检查会话是否关闭,若关闭则不再激活
-
计算会话新超时时间
若会话未关闭,计算该会话下一次超时时间点 ExpirationTime_New
-
定位会话当前区块
根据该会话当前超时时间 ExpirationTime_Old 定位其所在区块
-
迁移会话
将该会话从老区块取出,放入 ExpirationTime_New 对应的新区块
-
-
会话超时检查
SessionTracker 按区块(桶)的时间点批量检查同一区块内的过期会话,提高效率和性能
会话清理
-
标记会话状态为 “已关闭”
防止清理期间处理客户端新请求
-
发起 “会话关闭” 请求
为了使会话关闭操作在集群内都生效,ZooKeeper 提交会话关闭请求
-
收集待清理的临时节点
某个会话失效后,和该会话相关的临时节点需一并清除掉
ZooKeeper 根据该会话 sessionID 获取对应临时节点列表
-
添加 “节点删除” 事务变更
完成相关临时节点收集后,将这些临时节点转换成 “节点删除” 请求,放入事务变更队列
-
删除临时节点
-
移除会话
节点删除后,移除 SessionTracker 中的会话信息
-
关闭 NIOServerCnxn
重连
客户端和服务端网络断开时,ZooKeeper 客户端会自动反复尝试重连
-
连接断开: CONNECTION_LOSS
网络闪断或当前连接的服务器故障,客户端会从地址列表内选取新地址尝试重连
-
会话失效: SESSION_EXPIRED
重连期间耗时过长,超过会话超时时间,服务端将该会话清理
-
会话转移: SESSION_MOVED
客户端重连后,可能是新的服务端,服务端会检查会话所有者(Owner),防止出现会话冲突