【☀Zookeeper】ZooKeeper面试题

ZooKeeper 是什么?

ZooKeeper 是一个分布式协调服务,为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案。分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。这些功能的实现主要依赖于 ZooKeeper 提供的 数据存储+事件监听 功能。

ZooKeeper 特点

  • 顺序一致性:从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
  • 原子性:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
  • 单一系统映像:无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
  • 可靠性:一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。

ZooKeeper 应用场景

典型的应用场景有:

  • 集群管理:监控节点存活状态、运行请求等
  • 主节点选举:主节点挂掉了之后可以从备用的节点开始新一轮选主,主节点选举说的就是这个选举的过程,使用 Zookeeper 可以协助完成这个过程
  • 命名服务:在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息
  • 分布式ID:可以通过 ZooKeeper 的顺序节点生成全局唯一 ID
  • 数据发布/订阅:通过 Watcher 机制 可以很方便地实现数据发布/订阅。当你将数据发布到 ZooKeeper 被监听的节点上,其他机器可通过监听 ZooKeeper 上节点的变化来实现配置的动态更新
  • 分布式锁:通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁。分布式锁的实现也需要用到 Watcher 机制

实际上,这些功能的实现基本都得益于 ZooKeeper 可以保存数据的功能,但是 ZooKeeper 不适合保存大量数据,这一点需要注意。

Kafka中的控制器选举工作依赖于ZooKeeper ,成功竞选成为控制器的broker会在ZooKeeper 中创建/controller临时节点

ZooKeeper 的重要概念

Data model(数据模型)

ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都可以存储数据,这些数据可以是数字、字符串或者是二级制序列。每个节点还可以拥有 N 个子节点,最上层是根节点以“/”来代表。每个数据节点在 ZooKeeper 中被称为 znode,它是 ZooKeeper 中数据的最小单元。每个 znode 都一个唯一的路径标识。

ZooKeeper 主要是用来协调服务的,而不是用来存储业务数据的,所以不要放比较大的数据在 znode 上,ZooKeeper 给出的上限是每个结点的数据大小最大是 1M

ZooKeeper 节点路径标识方式和 Unix 文件系统路径非常相似,都是由一系列使用斜杠"/"进行分割的路径表示,开发人员可以向这个节点中写入数据,也可以在节点下面创建子节点。

znode(数据节点)

每个数据节点在 ZooKeeper 中被称为 znode,它是 ZooKeeper 中数据的最小单元。

我们通常是将 znode 分为 4 大类:

  • 持久(PERSISTENT)节点 :一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
  • 临时(EPHEMERAL)节点 :临时节点的生命周期是与客户端会话(session) 绑定的,会话消失则节点消失 。并且临时节点只能做叶子节点 ,不能创建子节点
  • 持久顺序(PERSISTENT_SEQUENTIAL)节点 :除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 /node1/app0000000001 、/node1/app0000000002 。
  • 临时顺序(EPHEMERAL_SEQUENTIAL)节点 :除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性

每个 znode 由 2 部分组成:

  • stat:状态信息
  • data:节点存放的数据的具体内容

stat包含了一个数据节点的所有状态信息的字段,包括事务 ID(cZxid)、节点创建时间(ctime) 和子节点个数(numChildren) 等等。

  • cZxid:该数据节点被创建时的事务 id
  • ctime:该节点的创建时间
  • mZxid:该节点最后一次更新时的事务 id
  • mtime:该节点最后一次的更新时间
  • pZxid:该节点的子节点列表最后一次修改时的事务 id,只有子节点列表变更才会更新 pZxid,子节点内容变更不会更新
  • cversion:子节点版本号,当前节点的子节点每次变化时值增加 1
  • dataVersion:数据节点内容版本号,节点创建时为 0,每更新一次节点内容(不管内容有无变化)该版本号的值增加 1
  • aclVersion:节点的 ACL 版本号,表示该节点 ACL 信息变更次数
  • ephemeralOwner:创建该临时节点的会话的 sessionId;如果当前节点为持久节点,则 ephemeralOwner=0
  • dataLength:数据节点内容长度
  • numChildren:当前节点的子节点个数

Watcher(事件监听器)

Watcher(事件监听器),是 ZooKeeper 中的一个很重要的特性。ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。

注意:Watcher 事件的触发都是一次性的,比如客户端通过getData('/node',true)注册监听,如果/node节点发生数据修改,那么该客户端会收到一个修改时间通知,但是/node再次发生改变时,客户端无法收到Watcher事件,为了解决这个问题,客户端必须在收到的事件回调中再次注册事件。

会话(Session)

Session 可以看作是 ZooKeeper 服务器与客户端的之间的一个 TCP 长连接,通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向 ZooKeeper 服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的 Watcher 事件通知。

Session 有一个属性叫做:sessionTimeout ,sessionTimeout 代表会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在sessionTimeout规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。

另外,在为客户端创建会话之前,服务端首先会为每个客户端都分配一个 sessionID。由于 sessionID是 ZooKeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的,因此,无论是哪台服务器为客户端分配的 sessionID,都务必保证全局唯一。

ZooKeeper 集群角色

在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了 Leader、Follower 和 Observer 三种角色。如下图所示:

ZooKeeper 集群中的所有机器通过一个 Leader 选举过程 来选定一台称为“Leader”的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。

  • Leader:为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。
  • Follower:为客户端提供读服务,如果是写服务则转发给 Leader。参与选举过程中的投票。
  • Observer:为客户端提供读服务,如果是写服务则转发给 Leader。不参与选举过程中的投票,也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能。此角色于 ZooKeeper3.3 系列新增的角色。

ZooKeeper 集群 Leader 选举过程

Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,就会进入 Leader 选举过程,这个过程会选举产生新的 Leader 服务器。

ZooKeeper 服务器的四种状态:

  • looking:寻找Leader的状态,当前集群没有leader
  • leading:成为一个leader节点。
  • following:成为一个follower节点。
  • observing:当前服务角色是一个observer。

ZooKeeper集群启动时的选举

假如有三个节点(s1,s2,s3)组成的集群。在集群启动过程中,当有一台ZooKeeper节点s1启动完成后,此时集群中只有一个节点无法进行leader的选举。当第二个节点s2启动成功后,此时两个节点可以正常通信,进入leader的选举过程,具体如下:

1)每一个节点都是自私的,各自都投自己1票。每次投票都会包含选举服务器的myid和zxid,投票结果使用(myid,zxid)表示,此时s1的投票为(1,0),s2为(2,0)。然后各自投票给集群中其他节点。

2)集群中的每个节点都接收其他节点的投票,s1将(1,0)发给s2,s2将(2,0)发给s1。

3)PK投票结果,对于每一次投票,服务器都会将自己的投票结果和其他节点做比较:

  • 首先检查zxid,zxid大的优先作为leader。
  • 如果zxid相同,则比较myid,myid大的优先作为leader。

对于s1,它的投票是(1,0),接收到s2的投票(2,0),首先它会比较自己的zxid和s2的zxid,此时相等,然后比较myid,s2的myid大,s1更新自己的投票结果为(2,0),然后重新投票,对于s2收到s1的投票(1,0),显然s2胜出,无需更新自己的投票结果。

4)统计投票。每次投票结束后,服务器都会统计投票信息,判断是否已经有过半机器接收到相同的投票信息,对于s1,s2,都统计出集群中已经有两台机器是(2,0)的投票信息,此时认为leader已经产生。

5)改变服务器状态。一旦确定了leader,每个节点都会更改自己的状态,胜出的leader将状态改为leading,败者follower更改自己的状态为following。

ZooKeeper集群运行中的leader选举

假如有三台服务器(s1,s2,s3)组成的集群,s2是leader。在集群运行中时,只有当集群中的leader宕机才会触发leader的重新选举,集群中follower宕机或者新节点的加入并不影响leader的地位。

选举过程如下:

1)状态更改。leader宕机之后,各节点将自己的状态更改为looking,然后进入leader的选举。

2)每个节点都会发出投票,运行期间每个节点的zxid可能不同,假设s1的zxid为122,s3的zxid也为122,在第一轮投票中s1,s2都会投自己,产生投票s1(1,122),s2(3,122),然后各自将投票发送给其他节点。

3)接收来自其他节点的投票。

4)处理投票。PK投票,与启动时相同,此时s3胜出。

5)统计投票。s1,s3的投票都为(3,122),已经有过半机器是(3,122),s3成为leader。

6)更改状态。s1,s3更改自己的状态,s1修改为following,s3修改为leading

ZooKeeper 集群为什么最好奇数台?

ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。假如我们的集群中有 n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n/2。

比如假如我们有 3 台,那么最大允许宕掉 1 台 ZooKeeper 服务器,如果我们有 4 台的的时候也同样只允许宕掉 1 台。 假如我们有 5 台,那么最大允许宕掉 2 台 ZooKeeper 服务器,如果我们有 6 台的的时候也同样只允许宕掉 2 台。

ZooKeeper 选举的过半机制防止脑裂

什么是集群脑裂

对于一个集群,通常多台机器会部署在不同机房,来提高这个集群的可用性。保证可用性的同时,会发生一种机房间网络线路故障,导致机房间网络不通,而集群被割裂成几个小集群。这时候子集群各自选主导致“脑裂”的情况。

举例说明:比如现在有一个由 6 台服务器所组成的一个集群,部署在了 2 个机房,每个机房 3 台。正常情况下只有 1 个 leader,但是当两个机房中间网络断开的时候,每个机房的 3 台服务器都会认为另一个机房的 3 台服务器下线,而选出自己的 leader 并对外提供服务。若没有过半机制,当网络恢复的时候会发现有 2 个 leader。仿佛是 1 个大脑(leader)分散成了 2 个大脑,这就发生了脑裂现象。脑裂期间 2 个大脑都可能对外提供了服务,这将会带来数据一致性等问题。

过半机制是如何防止脑裂现象产生的?

ZooKeeper 的过半机制导致不可能产生 2 个 leader,因为少于等于一半是不可能产生 leader 的,这就使得不论机房的机器如何分配都不可能发生脑裂

ZAB 协议和 Paxos 算法

ZooKeeper 并没有完全采用 Paxos 算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外,ZAB 协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为 ZooKeeper设计的崩溃可恢复的原子消息广播算法。

ZAB 协议

ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。

ZAB协议的两种基本的模式:崩溃恢复和消息广播

  • 崩溃恢复 :当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致。
  • 消息广播 :当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。 当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。

说说ZooKeeper的文件系统

ZooKeeper 提供一个多层级的节点命名空间(节点称为 znode)。与文件系统不同的是,这些节点都可以设置关联的数据,而文件系统中只有文件节点可以存放数据而目录节点不行。

ZooKeeper 为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得 Zookeeper 不能用于存放大量的数据,每个节点的存放数据上限为1M。

ZooKeeper 是如何保证事务的顺序一致性的?

ZooKeeper 节点宕机如何处理?

  • 单机部署:一台集群上运行;
  • 集群部署:多台集群运行;
  • 伪集群部署:一台集群启动多个 Zookeeper 实例运行。

集群最少要几台机器,集群规则是怎样的?集群中有 3 台服务器,其中一个节点宕机,这个时候 Zookeeper 还可以使用吗?

集群支持动态添加机器吗?

posted @ 2023-04-05 16:16  残城碎梦  阅读(183)  评论(0)    收藏  举报