分布式学习手记08 - ZooKeeper 核心技术(中)

3 分钟读完

ZooKeeper 的核心技术细节第二部分,主要是服务端工作原理,如:服务器启动流程、Leader 选举、各服务器角色介绍。

服务器启动

五个主要步骤:配置文件解析、初始化数据管理器、初始化网络I/O管理器、数据恢复和对外服务,单机和集群模式的启动流程有所差别。

单机版服务器启动

预启动

  1. 由 QuorumPeerMain 作为启动类

  2. 解析配置文件 zoo.cfg

  3. 创建并启动历史文件清理器 DatadirCleanupManager

    包括对事务日志和快照数据文件进行定时清理

  4. 判断当前是集群模式还是单机模式的启动

    若是单机模式,委托给 ZooKeeperServerMain 进行启动处理

  5. 再次解析 zoo.cfg

  6. 创建服务器实例 ZooKeeperServer

    实例创建后,对该实例进行初始化工作:包括连接器、内存数据库、请求处理器等组件的初始化

初始化

  1. 创建服务器统计器 ServerStats

    采集服务器基本的运行时信息

  2. 创建数据管理器 FileTxnSnapLog

    FileTxnSnapLog 是 ZooKeeper 上层服务器和底层数据存储之间的对接层,提供操作数据文件的接口,包括事务日志文件和快照数据文件

  3. 设置服务器 tickTime 和会话超时时限

  4. 创建 ServerCnxnFactory

    可以指定使用内部 NIO 或者 Netty 框架来作为 ZooKeeper 服务端网络连接工厂

  5. 初始化 ServerCnxnFactory

  6. 启动 ServerCnxnFactory 主线程

  7. 恢复本地数据

    ZooKeeper 启动时,需要从本地快照和事务日志中进行数据恢复

  8. 创建并启动会话管理器 SessionTracker

  9. 初始化 ZooKeeper 的请求处理链

  10. 注册 JMX 服务

    服务器运行时相关信息以 JMX 的方式暴露给外部

  11. 注册 ZooKeeper 服务器实例

    将初始化完毕的 ZooKeeper 实例注册给 ServerCnxnFactory,开始对外提供服务

集群版服务器启动

预启动

  1. 由 QuorumPeerMain 作为启动类

  2. 解析配置文件 zoo.cfg

  3. 创建并启动历史文件清理器 DatadirCleanupManager

  4. 判断当前是集群模式还是单机模式的启动

初始化

  1. 创建 ServerCnxnFactory

  2. 初始化 ServerCnxnFactory

  3. 创建数据管理器 FileTxnSnapLog

  4. 创建 QuorumPeer 实例

    Quorum 是集群模式下服务器实例的托管者,运行期间 QuorumPeer 会不断检测服务器实例运行状态,根据情况发起 Leader 选举

  5. 创建内存数据库 ZKDatabase

    ZKDatabase 管理所有会话记录、DataTree 和事务日志的存储

  6. 初始化 QuorumPeer

    将核心组件注册到 QuorumPeer 中去,并配置集群相关参数

  7. 恢复本地数据

  8. 启动 ServerCnxnFactory 主线程

Leader 选举

  1. 初始化 Leader 选举

    创建 Leader 选举所需的网络 I/O 层 QuorumCnxManager,启动选举端口监听

  2. 注册 JMX 服务

  3. 检测当前服务器状态

    由 QuorumPeer 不断检测当前服务器的状态,做出相应处理

  4. Leader 选举

    集群内机器互相进行一系列投票,选举产生 Leader,其余机器成为 Follower 或 Observer

Leader 和 Follower 启动期交互过程

  1. 创建 Leader 服务器和 Follower 服务器

    选举完成后,各服务器根据自身角色创建相应实例,进入该角色主流程

  2. Leader 服务器启动信息接收器 LearnerCnxAcceptor

    LearnerCnxAcceptor 负责所有非 Leader 服务器(以下称为 Learner)的连接请求

  3. Learner 服务器和 Leader 建立连接

  4. Leader 服务器创建 LearnerHandler

    Leader 接收到其他机器连接创建请求后,对每一个 Leader 与 Learner 的连接,创建一个 LearnerHandler 实例,负责二者间消息通信和数据同步

  5. 向 Leader 注册

    Learner 向 Leader 发送自己的基本信息 LearnerInfo

  6. Leader 解析 Learner 信息,计算新的 epoch

  7. 发送 Leader 状态

  8. Learner 发送 ACK 消息

  9. 数据同步

  10. 启动 Leader 和 Learner 服务器

    当有过半的 Learner 完成了数据同步,则 Leader 和 Learner 服务器实例可以开始启动了

Leader 和 Follower 启动

  1. 创建并启动会话管理器

  2. 初始化 ZooKeeper 的请求处理链

  3. 注册 JMX 服务

Leader 选举

选举概述

  • 服务器启动时期的 Leader 选举

    服务器启动后互相建立通信,每台服务器都试图找到一个 Leader,于是进入选举流程

    1. 服务器发起投票

      初始情况下,每台服务器会将自己作为 Leader 生成投票,形式为(SID, ZXID),发送给集群中其他所有机器

    2. 接收投票

      接收其他节点发来的投票,判断其有效性

    3. 处理投票

      比对自有票和外来票:

      • 先比较 ZXID,选择较大的;

      • ZXID 相同,再比较 SID,选择较大的

      若外来票较优,则更新自有票为外来票,再次发送;若自有票较优,则不更新,再次发送

    4. 统计投票

      每轮投票后,服务器会统计所有投票,当有有过半机器接收到相同投票,则选出 Leader

    5. 改变服务器状态

      Leader 确定后,各服务器确定自己的状态:Leader(LEADING), Follower(FOLLOWING)

  • 服务器运行期间的 Leader 选举

    一旦 Leader 节点挂了,集群暂时无法对外服务,进入新一轮选举

    1. 变更状态

      非 Observer 节点状态变为 LOOKING,进入 Leader 选举流程

    2. 服务器发出投票

    3. 接收投票

    4. 处理投票

    5. 统计投票

    6. 改变服务器状态

Leader 选举的算法分析

目前 ZooKeeper 只保留 TCP 版本的 FastLeaderElection 选举算法

  1. 进入 Leader 选举

    服务器初始化启动,或运行期间无法和原 Leader 保持连接,则进入选举流程

    若集群中已有 Leader,刚启动的服务器试图进行选举时,会被告知 Leader 信息,进而和 Leader 节点建立连接,进行状态同步

  2. 开始第一次投票

    进入 LOOKING 状态的个节点开始发起投票,形如(SID, ZXID),即(服务器ID + 事务ID)

    第一次投票各节点均推举自己

  3. 变更投票

    收到来自集群其他节点投票,与自投票比对,根据先 ZXID、 再 SID 原则决定是否需要变更自投票,处理完成后发送出去

  4. 确定 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 选举过程中的网络通信

    1. 消息队列

      • recvQueue: 消息接收队列
      • queueSendMap: 消息发送队列,保存待发送消息
      • senderWorkerMap: 发送器集合,每个发送器对应一台远程节点
      • lastMessageSent: 最近发送过的消息
    2. 建立连接

      QuorumCnxManager 启动时,创建一个 ServerSocket 来监听 Leader 选举的通信端口(3888),接收其他服务器的创建连接请求,建立连接

      连接建立后,根据远程服务器 SID 创建并启动相应的消息发送器 SendWorker 和消息接收器 RecvWorker

    3. 消息接收与发送

      RecvWorker 从 TCP 连接中读取消息,将其保存到 recvQueue 队列中

      SendWorker 从对应的消息发送队列中获取一个消息发送,之后将该消息放入 lastMessageSent

    4. 选票管理

      • sendqueue: 选票发送队列,用于保存待发送的选票
      • recvqueue: 选票接收队列,用于保存接收到的外部投票
      • WorkerReceiver: 选票接收器,不断从 QuorumCnxManager 中获取外来选举消息,转换成选票存入 recvqueue
      • WorkerSender: 选票发送器,不断从 sendqueue 队列中获取待发送的选票,传递到底层 QuorumCnxManager 中去
    5. 算法核心

      lookForLeader 方法的流程

      1. 自增选举轮次

        通过 logicalclock 属性,标记当前选举轮次,ZooKeeper 规定有效的投票必须在同一轮次中

        每轮投票开始时,首先对 logicalclock 执行一次自增

      2. 初始化选票

        根据自身信息生成 Vote

      3. 初始化选票放入 sendqueue,由 WorkerSender 发送出去

      4. 从 recvqueue 获取外部投票

      5. 判断选举轮次

        根据 logicalclock 处理投票:

        • 外部投票的选举轮次大于内部投票: 更新自己 logicalclock,清空所有已收到的投票,再使用初始化的投票与外部投票比较,执行投票变更;

        • 外部投票的选举轮次小于内部投票: 忽略该外部投票,重新获取投票;

        • 内外投票的选举轮次一致: 选票有效,进行比较

      6. 选票比较

        比较 ZXID、SID

      7. 变更投票

        根据比较结果,执行变更处理,发送出去

      8. 投票归档

        将收到的外部投票放入选票集合 recvset 进行归档

      9. 统计投票

        确定是否有过半节点认可当前投票:若有则终止投票,短期等待没有更优投票就进入下一步;否则继续获取外部投票

      10. 更新服务器状态

        根据最终角色,进入相应状态

服务器角色

Leader

  • 主要功能

    1. 事务请求的唯一调度和处理者,保证集群事务处理的顺序性;

    2. 集群内部各服务器的调度者

  • 请求处理链

                         |--> [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

  • 主要功能

    1. 处理客户端非事务请求,转发事务请求给 Leader 服务器;

    2. 参与事务请求 Proposal 的投票;

    3. 参与 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

分类:

更新时间: