Java 面试题总结之 Zookeeper 篇

文章目录

  • 文章地址链接
  • 概述
  • zookeeper 角色
    • Leader
    • Follower
    • Observer
  • Zookeeper 的文件系统
  • Zookeeper 中 ZAB 协议
    • 事务编号 Zxid(事务请求计数器+ epoch)
    • epoch
    • Zab 协议模式
    • Zab 协议阶段
  • Zookeeper 的工作原理
  • Zookeeper 的投票机制
  • Zookeeper 的 Server 工作状态
  • Zookeeper 的选举过程
  • Zookeeper 的 Watcher 机制
    • Watcher 的工作机制
      • 客户端注册 Watcher 实现
      • 服务端处理 Watcher 实现
      • 客户端回调 Watcher
    • Watcher 机制的特性
  • Zookeeper 的同步机制
    • Zookeeper 的同步机制分类
  • Zookeeper 的数据节点
  • Zookeeper 宕机
  • Zookeeper 对节点的监听通知是否是永久的, 说明其原因


文章地址链接

分类 博客链接
Java 面试题总结之基础篇 待完成…
Java 面试题总结之并发编程篇 https://blog.csdn.net/weixin_38251871/article/details/104667961
Java 面试题总结之常用设计模式篇 https://blog.csdn.net/weixin_38251871/article/details/104658445
Java 面试题总结之数据库篇 待完成…
Java 面试题总结之 Spring 篇 待完成…
Java 面试题总结之 Spring Boot 篇 待完成…
Java 面试题总结 之 Spring Cloud 篇 待完成…
Java 面试题总结之 Dubbo 篇 待完成…
Java 面试题总结之 Redis 篇 待完成…
Java 面试题总结之 Zookeeper 篇 https://blog.csdn.net/weixin_38251871/article/details/104741902
Java 面试题总结之消息中间件篇 待完成…
Java 面试题总结之 Nginx 篇 待完成…
Java 面试题总结之分布式事务篇 待完成…
Java 面试题总结之分布式锁篇 待完成…

概述

  • Zookeeper : 是一个开放源代码的分布式服务, 它是集群的管理者, 监视者集群中各个节点的状态并根据节点提交的反馈进行下一步的工作, 最终将简单易用的接口和性能高效、功能稳定的系统提供给用户.
  • Zookeeper 提供了一种类似于 Linux 文件系统的树形结构, 提供了对每个节点的监控与通知机制.
  • 分布式应用程序可以基于 Zookeeper 实现, 例如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能.

zookeeper 角色

  • 一个 Zookeeper 集群是基于主从复制的高可用集群, 每个服务器都有三个角色, 分别是LeaderFollowerObserver

Java 面试题总结之 Zookeeper 篇_第1张图片

Leader

  • 一个 Zookeeper 集群在同一时间只有一个工作的 Leader, 它会发起并维护各个 FollowerObserver 角色之间的心跳, 所有的写操作都是通过 Leader 角色完成再原子广播给其他发服务器, 只要超过半数的节点 (不包括 Observer 节点) 写入成功, 该写请求就会被提交

Follower

  • 一个 Zookeeper 集群可能同一时刻存在 nFollower 角色, Follower 角色会响应 Leader 的心跳, Follower 可以直接处理并返回客户端的请求, 同时会将写请求转发给 Leader 处理, 并负责在 Leader 处理写请求的时候对请求进行投票

Observer

  • Observer 角色类似于 Follower, 但是它没有投票的权利. 当 Zookeeper 需要保证高可用和强一致性, 为了支持更多的客户端, 需要增加更多的 Server. 当 Server 增多后, 在投票阶段延迟增大, 就会影响性能. 所以在这种情况之下就引入了 Observer 角色, 该角色不参与投票. 在 Observer 接收到写请求的时候, 会把该请求转发给 Leader 节点. 所以加入更多的 Observer 角色会提高 Zookeeper 的伸缩性且不会影响吞吐率

Zookeeper 的文件系统

  • Zookeeper 提供了一个多层级的节点命名空间 (znode), 与文件系统不同的是, 这些节点都可以设置关联的数据, 在文件系统中只有文件节点才可以存放数据, 而目录节点不行
  • Zookeeper 为了保证高吞吐和低延迟, 在内存中维护了这个树形结构的目录, 而这种特性使 Zookeeper 不能存放大量的数据, 每个节点存放的数据最多为 1M

Zookeeper 中 ZAB 协议

事务编号 Zxid(事务请求计数器+ epoch)

  • ZAB 协议的事务编号 Zxid 设计中, Zxid 是一个 64 位的数字, 高 32 位代表着 Leader 周期的 epoch 编号, 每个当选产出新的 Leader 服务器, 就会从这个 Leader 服务器中取出其本地日志中最大事务的 Zxid, 并读取其中的 epoch 值, 然后进行加 1, 作为 epoch 的新值, 低 32 位代表一个递增的计数器, 主要是针对客户端每一个事务请求时, 计数器加 1 , Zxid 用于标识一次更新操作的 Proposal id, 为了保证顺序性, Zxid 需要单调自增

epoch

  • 指当前集群所处的年代或者周期, 每个 Leader 都有自己的一个编号, 在每次选举之后并且 Leader 进行了变更, 都会在前一个基础之上加 1, 所以在旧的 Leader 角色崩溃之后, 就不会有其他的 Learner 角色听从该旧的 Leader 角色, 因为 Learner 角色只是听从于当前的 Leader 角色

Zab 协议模式

  • 恢复模式(选主) : 在服务启动或者 Leader 挂掉的时候, ZAB 就进入到了恢复模式
  • 广播模式(同步数据) :Leader 被选取出来之后, 并且多数的 ServerLeader 完成状态同步之后, 就结束恢复模式, 状态同步保证了 LeaderServer 有相同的系统状态

Zab 协议阶段

  • 选举阶段 : 当服务启动之后, 节点都处于选举阶段, 只要有一个节点得到了半数节点的票数, 就会被选举为准 Leader 角色, 只有到达了广播阶段, 准 Leader 角色才会变成 Leader 角色, 选举阶段的目的是为了选举出一个准 Leader, 然后为下一个阶段做准备
  • 发现阶段 : 各个 Follower 与准 Leader 进行通信, 同步 Follower 最接接收到的事务 Proposal, 主要是发现当前大多数节点接收到的最新的提议, 并且准 Leader 生成新的 epoch 新值让各个 Follower 接受并更新它们的 accepted epoch 值. 一个 Follower 只会连接一个 Leader, 如果有一个 Follower A 认为另一个 Follower BLeader, Follower A 在尝试连接 Follower B 时就会被拒绝, 然后就会进入选举阶段
  • 同步阶段 : 主要是利用 Leader 前一阶段获取的最新 Proposal 记录, 并且将其同步给所有集群中的副本, 只有当大多数节点都同步完成之后, 准 Leader 就会变成的 Leader 角色, Follower 只会接受 Zxid 比自己的 lastZxid 大的提议
  • 广播阶段 : 在处于该阶段时, Zookeeper 集群才能够正式地对外提供事务服务, 并且 Leader 可以进行消息广播, 如果还有新的节点加入, 就对新节点进行同步, 在 ZAB 中提交事务只需得到超过半数的节点的 ACK 就可以进行操作.

Zookeeper 的工作原理

  • Zookeeper 的核心是原子广播, 该机制保证了各个 Server 之间的同步, 实现这个机制的是 ZAB协议, 在上面中有提到 ZAB 协议有恢复模式和广播模式
  • 在服务启动或者 Leader 崩溃之后, ZAB 就进入恢复模式, 当 Leader 角色被选举出来的时候, 并且大多数 Server 完成了与 Leader 状态同步之后, 恢复模式就结束了, 状态同步保证了 ServerLeader 的系统状态一致
  • Leader 与大多数 Learner 进行状态同步之后, 就进入广播状态. 当一个新的 Server 加入到 Zookeeper 服务中, 就会在恢复模式下启动, 在发现 Leader 的同时就与其进行状态同步, 直到同步结束之后, 也参与到消息广播. Zookeeper Server 会一直维持在广播状态, 直到 Leader 崩溃或者失去大量的 Follower 角色支持
  • 广播模式需要保证 Proposal 被按照顺序处理, 所以 Zookeeper 采用了递增的事务 Zxid 来保证其顺序性, 所有的 Proposal 在提出时都会加上 Zxid
  • Zxid 的实现与之前写的一致, Zxid 是一个 64 位的数字, 高 32 位是 epoch 用来标识 Leader 关系是否改变, 每次一个 Leader 被选举出来之后, 都会有一个新的 epoch 值, 低 32 位是个递增的计数器
  • Leader 崩溃或者失去大部分的 Follower 支持时, 这个时候 Zookeeper 就进入到了恢复模式, 恢复模式又需要重新选出 Leader 角色, 然后让所有的 Server 恢复到正确的状态

Zookeeper 的投票机制

  • 每个 Server 启动之后都会询问其他 Server 它需要给谁进行投票, 当对其他的 Server 进行询问时, 一般 Server 每次根据自己的状态都回复自己推荐的 LeaderId 和上一次处理事务的 zxid, 所以当 Server 启动的时候都会首先给自己先投票
  • 当收到所有的 Server 回复之后, 就计算出 zxid 最大的是哪个 Server, 并将这个 Server 相关的信息设置成下一次要投票的 Server
  • 在计算过程中获得票数最多的 Server 为获胜者, 如果获胜者的票数超过半数, 就将 Server 选为 Leader, 否则继续这个过程, 直到 Leader 被选举出来
  • 在选取出 Leader 节点之后就会开始等待 Server 的连接
  • Follower 连接 Leader, 将最大的 zxid 发送给 Leader 角色
  • Leader 根据 Followerzxid 确定同步点后就结束选举阶段
  • 在选举阶段完成 Leader 同步之后就通知 Follower 已经成为了 uptodate 状态
  • Follower 收到 uptodate 消息之后, 就可以重新接受客户端的请求进行服务

Zookeeper 的 Server 工作状态

Zookeeper 中主要有四种状态: LOOKING、FOLLOWING、LEADING、OBSERVING

  • LOOKING : 寻找 Leader 角色, 当服务器处于 LOOKING 状态的时候, 认为当前集群中没有 Leader 角色, 所以要进入 Leader 选举
  • FOLLOWING : 表示当前服务器的角色是 Follower
  • LEADING : 表示当前服务器的角色是 Leader
  • OBSERVING : 表示当前服务器的角色是 Observer

Zookeeper 的选举过程

前提 : 目前有 4 台服务器, 没有服务器上都没有数据, 他们分别是 A、B、C、D, 并按照编号依次启动
选举的过程 :

  • 服务器 A 启动后首先给自己投票, 然后发投票信息, 因为还没有机器启动, 所以收不到任何的反馈信息, 服务器 A 的状态就一直处于 LOOKING 状态;
  • 服务器 B 启动后也首先给自己投票, 然后和启动的服务器 A 交换结果, 因为服务器 B 的编号比服务器 A 的编号大, 所以服务器 B 胜出. 但是现在的投票数量还没有大于半数, 只有两个服务器, 所以两个服务器的状态还是处于 LOOKING;
  • 服务器 C 启动后同样给自己投票, 然后和启动的服务器 A、B 交换结果, 由于服务器 C 的编号比服务器 A、B 的编号大, 所以服务器 C 胜出, 此时此刻的投票数量正好大于了半数, 所以服务器 C 成为了 Leader, 服务器 A、B 成为了 Follower;
  • 服务器 D 启动并给自己投票之后, 同时和服务器 A、B、C 交换结果, 虽然服务器 D 的编号最大, 但是服务器 C 已经成为了 Leader, 所以服务器 D 只能成为 Follower;

Zookeeper 的 Watcher 机制

  • Zookeeper 允许客户端向服务端的某个 Znode 节点注册一个 Watcher 监听, 当服务端的一些指定事件触发了 Watcher, 服务端会向指定的客户端发送一个通知事件来实现分布式的通知功能, 然后客户端根据 Watcher 的通知状态和事件类型做出业务上的改变

Watcher 的工作机制

客户端注册 Watcher 实现

  • 调用 getData()、getChildren()、exist()API 并传入 Watcher 对象
  • 标记请求 request并封装 WatcherWatchRegistration
  • 封装成 Packet 对象并向服务端发送 request
  • 当收到服务端的响应之后, 将 Watcher 注册到 ZKWatcherManager 中进行管理
  • 请求返回, 完成 Watcher 的注册

服务端处理 Watcher 实现

  • 当接受到客户端请求后, 处理请求判断是否需要注册 Watcher, 如果需要把数据节点的节点路径和 ServerCnxn 存储在 WatcherManagerWatchTablewatch2Paths 中;
    • ServerCnxn : 一个客户端和服务端的连接, 实现了 Watcherprocess 接口, 可以说是一个 Watcher 对象
  • 例如服务端接收到 setData() 事务请求触发 NodeDataChanged 事件
    • 首先: Watcher 的触发的过程是将通知状态(SyncConnected) 、事件类型 (NodeDataChanged) 和节点路径封装成一个 WatchedEvent 对象
    • 其次: 从 WatchTable 中根据节点路径查找 Watcher, 如果没有找到就说明客户端在该数据节点上注册过 Watcher. 当找到的情况下就提取出来并删除 WatchTableWatch2Paths 中对应的 Watcher
    • 最终: 调用 process 方法触发 Watcher 事件

客户端回调 Watcher

  • 客户端 SendThread 线程接收事件通知, 交给 EventThread 线程回调 Watcher, 客户端的 Watcher 机制同样是一次性的, 一旦被触发之后, 该 Watcher 就失效了

Watcher 机制的特性

  • 客户端串行执行 : 客户端 Watcher 回调的过程是一个串行同步的过程
  • 一次性 : 无论是客户端还是服务端, 当有一个 Watcher 被触发的时候, Zookeeper 都会将其相应存储给移除, 这样就会减轻服务端的压力, 否则对于经常更新的节点, 服务端会不断地向客户端发起事件通知, 对于网络和服务端的压力都比较大
  • 轻量 : Watcher 机制只会告诉客户端发生了什么机制, 而不是说明事件的具体内容, 在客户端向服务端注册 Watcher 的时候, 并不会把真实的 Watcher 对象实体发送给服务端, 只是在客户端请求中使用布尔类型的属性进行了标记
    Watcher 事件异步发送 Watcher 的通知事件是从 Server 发送到 Client 是异步的, 那么就会出现问题, 当不同的客户端和服务端通过 socket 通信时候, 因为网络延迟或者其他的因素导致客户端在不通的情况下监听到事件, 又因为 Zookeeper 本身提供了 ordering guarantee, 就是说在客户端监听了时间之后才感知到其所监视的节点发生了变化, 所以在我们使用 Zookeeper 的时候, 不可能期望监控到每次节点的变化, Zookeeper 只能保证最终一致性, 不能不保证强一致性
  • 注册 Watcher 的情况: 调用 getData()、getChildren()、exist()API
  • 触发 Watcher 的情况 调用 setData()、create()、delete()API
  • 在一个客户端连接到新的服务端的时候, Watcher 会用任意会话事件进行触发, 在和一个服务端失去连接的情况下, 是不能接收到 watch 的. 但是当 Client 重新连接的时候, 如果有需要, 之前所有的 watch 都会被重新注册, 只有对于一个未创建的 znodeexist watch, 如果在客户端断开连接的时候被创建了, 然后在客户端连接上钱删掉的情况下, 这个 watch 事件可能会被丢失

Zookeeper 的同步机制

  • 当集群完成了 Leader 选举之后, Learner 就会在 Leader 服务器中进行注册, 当 Learner 服务器完成注册之后就进入数据同步环节;
  • 在进行数据同步之前, Leader 服务器会完成数据同步的初始化, 在 Learner 服务器注册时发送的 ACKEPOCH 消息中提取 lastZxid
    • minCommittedLog : Learner 服务器中 Proposal 缓存队列 committedLog 中的最小 Zxid
    • maxCommittedLog : Learner 服务器中 Proposal 缓存队列 committedLog 中的最大 Zxid

Zookeeper 的同步机制分类

  • 直接差异化同步:
    • peerLastZxid 介于 minCommittedLogmaxCommittedLog 之间
  • 先回滚再差异化同步:
    • 当新的 Leader 服务器发现某个 Learner 服务器包含了自己没有的事务记录的情况下, 就会让该 Learner 服务器进行回滚到 Leader 服务器上存在的, 也是最接近 peerLastZxidZxid
  • 仅回滚同步:
    • perLastZxid 大于 maxCommittedLog 的情况下
  • 全量同步:
    • peerLastZxid 小于 minCommittedLog 情况下或者 Leader 服务器上没有 Proposal 缓存队列并且 peerLastZxid 不等于 lastProcessZxid 的情况下

Zookeeper 的数据节点

  • 临时节点 (EPHEMERAL) :其生命周期和客户端的会话绑定, 一旦客户端的会话失效, 这个客户端所创建的所有的临时节点就失效
  • 持久节点 (PERSISTENT) : 节点会一直存在 Zookeeper 上, 除非手动删除
  • 临时顺序节点 (EPHEMERAL_SEQUENTIAL) : 和临时节点类似, 只是加了顺序属性, 节点名称后面会追加一个父节点维护自增的整数型数字
  • 持久顺序节点 (PERSISTENT_SEQUENTIAL) : 和持久节点类似, 只是加了顺序属性, 节点名称后面会追加一个父节点维护自增的整数型数字

Zookeeper 宕机

  • Zookeeper 本身是个集群, 推荐配置不少于 3 个服务器, Zookeeper 自身需要保证在一个节点宕机的时, 其他节点还可以继续地提供服务, 如果是一个 Follower 宕机, 还有 2 台服务器提供访问, 因为 Zookeeper 上的数据是多副本的, 所以数据并不会丢失, 如果是 Leader 宕机, Zookeeper 会选出新的 Leader, Zookeeper 集群的机制只要超过了半数节点正常, 集群就能正常的提供服务, 只有当 Zookeeper 超过半数节点宕机时, 集群才会失效

Zookeeper 对节点的监听通知是否是永久的, 说明其原因

  • 不是永久的, 官方网站说明一个 Watch 事件是一个一次性的触发器, 当被设置 Watch 的数据发生修改的时候, 服务器就会发送这个修改给 Watch 的客户端后便于进行通知.
  • 为什么不设置成永久的原因, 假如服务端经常进行修改, 并且有很多客户端对服务端进行监听, 每次变动都需要通知到所有的客户端, 给网络和服务器造成了很大的压力

你可能感兴趣的:(面试)