分布式学习手记08 - ZooKeeper 核心技术(中)
ZooKeeper 的核心技术细节第二部分,主要是服务端工作原理,如:服务器启动流程、Leader 选举、各服务器角色介绍。
服务器启动
五个主要步骤:配置文件解析、初始化数据管理器、初始化网络I/O管理器、数据恢复和对外服务,单机和集群模式的启动流程有所差别。
单机版服务器启动
预启动
-
由 QuorumPeerMain 作为启动类
-
解析配置文件 zoo.cfg
-
创建并启动历史文件清理器 DatadirCleanupManager
包括对事务日志和快照数据文件进行定时清理
-
判断当前是集群模式还是单机模式的启动
若是单机模式,委托给 ZooKeeperServerMain 进行启动处理
-
再次解析 zoo.cfg
-
创建服务器实例 ZooKeeperServer
实例创建后,对该实例进行初始化工作:包括连接器、内存数据库、请求处理器等组件的初始化
初始化
-
创建服务器统计器 ServerStats
采集服务器基本的运行时信息
-
创建数据管理器 FileTxnSnapLog
FileTxnSnapLog 是 ZooKeeper 上层服务器和底层数据存储之间的对接层,提供操作数据文件的接口,包括事务日志文件和快照数据文件
-
设置服务器 tickTime 和会话超时时限
-
创建 ServerCnxnFactory
可以指定使用内部 NIO 或者 Netty 框架来作为 ZooKeeper 服务端网络连接工厂
-
初始化 ServerCnxnFactory
-
启动 ServerCnxnFactory 主线程
-
恢复本地数据
ZooKeeper 启动时,需要从本地快照和事务日志中进行数据恢复
-
创建并启动会话管理器 SessionTracker
-
初始化 ZooKeeper 的请求处理链
-
注册 JMX 服务
服务器运行时相关信息以 JMX 的方式暴露给外部
-
注册 ZooKeeper 服务器实例
将初始化完毕的 ZooKeeper 实例注册给 ServerCnxnFactory,开始对外提供服务
集群版服务器启动
预启动
-
由 QuorumPeerMain 作为启动类
-
解析配置文件 zoo.cfg
-
创建并启动历史文件清理器 DatadirCleanupManager
-
判断当前是集群模式还是单机模式的启动
初始化
-
创建 ServerCnxnFactory
-
初始化 ServerCnxnFactory
-
创建数据管理器 FileTxnSnapLog
-
创建 QuorumPeer 实例
Quorum 是集群模式下服务器实例的托管者,运行期间 QuorumPeer 会不断检测服务器实例运行状态,根据情况发起 Leader 选举
-
创建内存数据库 ZKDatabase
ZKDatabase 管理所有会话记录、DataTree 和事务日志的存储
-
初始化 QuorumPeer
将核心组件注册到 QuorumPeer 中去,并配置集群相关参数
-
恢复本地数据
-
启动 ServerCnxnFactory 主线程
Leader 选举
-
初始化 Leader 选举
创建 Leader 选举所需的网络 I/O 层 QuorumCnxManager,启动选举端口监听
-
注册 JMX 服务
-
检测当前服务器状态
由 QuorumPeer 不断检测当前服务器的状态,做出相应处理
-
Leader 选举
集群内机器互相进行一系列投票,选举产生 Leader,其余机器成为 Follower 或 Observer
Leader 和 Follower 启动期交互过程
-
创建 Leader 服务器和 Follower 服务器
选举完成后,各服务器根据自身角色创建相应实例,进入该角色主流程
-
Leader 服务器启动信息接收器 LearnerCnxAcceptor
LearnerCnxAcceptor 负责所有非 Leader 服务器(以下称为 Learner)的连接请求
-
Learner 服务器和 Leader 建立连接
-
Leader 服务器创建 LearnerHandler
Leader 接收到其他机器连接创建请求后,对每一个 Leader 与 Learner 的连接,创建一个 LearnerHandler 实例,负责二者间消息通信和数据同步
-
向 Leader 注册
Learner 向 Leader 发送自己的基本信息 LearnerInfo
-
Leader 解析 Learner 信息,计算新的 epoch
-
发送 Leader 状态
-
Learner 发送 ACK 消息
-
数据同步
-
启动 Leader 和 Learner 服务器
当有过半的 Learner 完成了数据同步,则 Leader 和 Learner 服务器实例可以开始启动了
Leader 和 Follower 启动
-
创建并启动会话管理器
-
初始化 ZooKeeper 的请求处理链
-
注册 JMX 服务
Leader 选举
选举概述
-
服务器启动时期的 Leader 选举
服务器启动后互相建立通信,每台服务器都试图找到一个 Leader,于是进入选举流程
-
服务器发起投票
初始情况下,每台服务器会将自己作为 Leader 生成投票,形式为(SID, ZXID),发送给集群中其他所有机器
-
接收投票
接收其他节点发来的投票,判断其有效性
-
处理投票
比对自有票和外来票:
-
先比较 ZXID,选择较大的;
-
ZXID 相同,再比较 SID,选择较大的
若外来票较优,则更新自有票为外来票,再次发送;若自有票较优,则不更新,再次发送
-
-
统计投票
每轮投票后,服务器会统计所有投票,当有有过半机器接收到相同投票,则选出 Leader
-
改变服务器状态
Leader 确定后,各服务器确定自己的状态:Leader(LEADING), Follower(FOLLOWING)
-
-
服务器运行期间的 Leader 选举
一旦 Leader 节点挂了,集群暂时无法对外服务,进入新一轮选举
-
变更状态
非 Observer 节点状态变为 LOOKING,进入 Leader 选举流程
-
服务器发出投票
-
接收投票
-
处理投票
-
统计投票
-
改变服务器状态
-
Leader 选举的算法分析
目前 ZooKeeper 只保留 TCP 版本的 FastLeaderElection 选举算法
-
进入 Leader 选举
服务器初始化启动,或运行期间无法和原 Leader 保持连接,则进入选举流程
若集群中已有 Leader,刚启动的服务器试图进行选举时,会被告知 Leader 信息,进而和 Leader 节点建立连接,进行状态同步
-
开始第一次投票
进入 LOOKING 状态的个节点开始发起投票,形如(SID, ZXID),即(服务器ID + 事务ID)
第一次投票各节点均推举自己
-
变更投票
收到来自集群其他节点投票,与自投票比对,根据先 ZXID、 再 SID 原则决定是否需要变更自投票,处理完成后发送出去
-
确定 Leader
获得超过半数(quorum)投票的机器成为 Leader
Leader 选举的实现细节
-
Vote 数据结构
即 org.apache.zookeeper.server.quorum.Vote 类
Vote 说明 - id: long 被推举的 Leader 的 SID - zxid: long 被推举的 Leader 的事务 ID - electionEpoch: long 逻辑时钟,记录选举周期的自增序列,标记服务器当前选举轮次 - peerEpoch: long 被推举的 Leader 的 epoch - state: ServerState 服务器当前的状态 -
QuorumCnxManager:网络 I/O
每台服务器会启动一个 QuorumCnxManager,负责各服务器之间的底层 Leader 选举过程中的网络通信
-
消息队列
- recvQueue: 消息接收队列
- queueSendMap: 消息发送队列,保存待发送消息
- senderWorkerMap: 发送器集合,每个发送器对应一台远程节点
- lastMessageSent: 最近发送过的消息
-
建立连接
QuorumCnxManager 启动时,创建一个 ServerSocket 来监听 Leader 选举的通信端口(3888),接收其他服务器的创建连接请求,建立连接
连接建立后,根据远程服务器 SID 创建并启动相应的消息发送器 SendWorker 和消息接收器 RecvWorker
-
消息接收与发送
RecvWorker 从 TCP 连接中读取消息,将其保存到 recvQueue 队列中
SendWorker 从对应的消息发送队列中获取一个消息发送,之后将该消息放入 lastMessageSent
-
选票管理
- sendqueue: 选票发送队列,用于保存待发送的选票
- recvqueue: 选票接收队列,用于保存接收到的外部投票
- WorkerReceiver: 选票接收器,不断从 QuorumCnxManager 中获取外来选举消息,转换成选票存入 recvqueue
- WorkerSender: 选票发送器,不断从 sendqueue 队列中获取待发送的选票,传递到底层 QuorumCnxManager 中去
-
算法核心
lookForLeader 方法的流程
-
自增选举轮次
通过 logicalclock 属性,标记当前选举轮次,ZooKeeper 规定有效的投票必须在同一轮次中
每轮投票开始时,首先对 logicalclock 执行一次自增
-
初始化选票
根据自身信息生成 Vote
-
初始化选票放入 sendqueue,由 WorkerSender 发送出去
-
从 recvqueue 获取外部投票
-
判断选举轮次
根据 logicalclock 处理投票:
-
外部投票的选举轮次大于内部投票: 更新自己 logicalclock,清空所有已收到的投票,再使用初始化的投票与外部投票比较,执行投票变更;
-
外部投票的选举轮次小于内部投票: 忽略该外部投票,重新获取投票;
-
内外投票的选举轮次一致: 选票有效,进行比较
-
-
选票比较
比较 ZXID、SID
-
变更投票
根据比较结果,执行变更处理,发送出去
-
投票归档
将收到的外部投票放入选票集合 recvset 进行归档
-
统计投票
确定是否有过半节点认可当前投票:若有则终止投票,短期等待没有更优投票就进入下一步;否则继续获取外部投票
-
更新服务器状态
根据最终角色,进入相应状态
-
-
服务器角色
Leader
-
主要功能
-
事务请求的唯一调度和处理者,保证集群事务处理的顺序性;
-
集群内部各服务器的调度者
-
-
请求处理链
|--> [E] CommitP | |--> [F] ToBeAppliedRequestP [A] PrepRequestP | |--> [G] FinalRequestP | | |--> [B] ProposalRequestP | |--> [C] SyncRequestP |--> [D] AckRequestP
A. PrepRequestProcessor 请求预处理器
识别当前客户端请求是否是事务请求(会改变服务器状态的请求,如创建节点、更新数据、删除节点或创建会话等),对于事务请求进行相关预处理
B. ProposalRequestProcessor 事务投票处理器
非事务请求,直接流转到 CommitProcessor
对于事务请求,除了将请求交给 CommitProcessor; 还会根据请求类型创建对应的 Proposal 提议,发送给 Follower 发起集群内事务投票; 并将请求交付给 SyncRequestProcessor 记录事务日志
C. SyncRequestProcessor 事务日志记录处理器
将事务请求记录到事务日志文件,同时触发 ZooKeeper 进行数据快照
D. AckRequestProcessor ACK反馈处理器
在 SyncRequestProcessor 完成事务日志记录后,向 Proposal 的投票收集器发送 ACK 反馈,通知已完成该 Prosoal 的事务日志记录
E. CommitProcessor 事务提交处理器
非事务请求,交付下一级处理器
对于事务请求,等待集群内完成投票直到该 Proposal 可被提交(自提交与集群提交匹配),控制对事务请求的顺序处理
F. ToBeCommitProcessor
使用 toBeApplied 队列存储已被 CommitProcessor 处理过的可被提交的 Proposal,将它们逐个交付给 FinalRequestProcessor,待其处理完成后,再将该 Proposal 从 toBeApplied 队列中移除
G. FinalRequestProcessor
客户端请求返回之前的收尾工作
-
LearnerHandler
Leader 通过为每一台 Follower/Observer 创建一个 LearnerHandler 实体的方式,与这些服务器建立 TCP 长连接
由 LearnerHandler 负责 Follower/Observer 与 Leader 之间的一系列网络通信,包括数据同步、请求转发和 Proposal 提议的投票等
Follower
-
主要功能
-
处理客户端非事务请求,转发事务请求给 Leader 服务器;
-
参与事务请求 Proposal 的投票;
-
参与 Leader 选举的投票
-
-
请求处理链
[A] FollowerRequestP --> CommitP --> FinalP {Leader Server} --> SyncRequestP --> [B] SendAckRequestP
A. FollowerRequestProcessor
识别当前请求是否是事务请求:若是事务请求,转发给 Leader 服务器;不是则往下级处理器流转
B. SendAckRequestProcessor
向 Leader 发送 ACK 反馈,表明已完成事务日志的记录
Observer
-
主要功能
非事务请求独立处理;事务请求转发给 Leader 服务器;不参与任何投票
即 Observer 只提供非事务服务,通常用于在不影响集群事务处理能力的前提下,提升集群的非事务处理能力
-
请求处理链
ObserverRequestP --> CommitP --> FinalP {Leader Server} --> SyncRequestP --> SendAckRequestP
集群间消息通信
集群各服务间的网络通信,都是通过不同类型的消息传递实现的
-
数据同步型
Learner 和 Leader 服务器进行数据同步时,网络通信所用到的消息
DIFF、TRUNC、SNAP、UPTODATE
-
服务器初始化型
整个集群或某些新机器初始化时,Leader 和 Learner 之间相互通信所使用的消息类型
LEADERINFO、FOLLOWERINFO、OBSERVERINFO、ACKEPOCH、NEWLEADER
-
请求处理型
REQUEST、PROPOSAL、ACK、COMMIT、INFORM、SYNC
-
会话管理型
PING、REVALIDATE