Zookeeper 面试常见 11 个连环炮
面试的时候,面试官只要看到你简历的上写的有 Zookeeper(熟悉、掌握)之类,那你至少要准备接下来的 11 连问。
NO1:说说 zookeeper 是什么?
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现(Chubby 是不开源的),它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户 。
Zookeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心,服务生产者将自己提供的服务注册到 Zookeeper 中心,服务的消费者在进行服务调用的时候先到 Zookeeper 中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据,简单示例图如下:
NO2:了解 Zookeeper 的系统架构吗?
ZooKeeper 的架构图中我们需要了解和掌握的主要有:
(1)ZooKeeper 分为服务器端(Server) 和客户端(Client),客户端可以连接到整个 ZooKeeper 服务的任意服务器上(除非 leaderServes 参数被显式设置, leader 不允许接受客户端连接)。
(2)客户端使用并维护一个 TCP 连接,通过这个连接发送请求、接受响应、获取观察的事件以及发送心跳。如果这个 TCP 连接中断,客户端将自动尝试连接到另外的 ZooKeeper 服务器。客户端第一次连接到 ZooKeeper 服务时,接受这个连接的 ZooKeeper 服务器会为这个客户端建立一个会话。当这个客户端连接到另外的服务器时,这个会话会被新的服务器重新建立。
(3)上图中每一个 Server 代表一个安装 Zookeeper 服务的机器,即是整个提供 Zookeeper 服务的集群(或者是由伪集群组成);
(4)组成 ZooKeeper 服务的服务器必须彼此了解。它们维护一个内存中的状态图像,以及持久存储中的事务日志和快照, 只要大多数服务器可用,ZooKeeper 服务就可用;
(5)ZooKeeper 启动时,将从实例中选举一个 leader,Leader 负责处理数据更新等操作,一个更新操作成功的标志是当且仅当大多数 Server 在内存中成功修改数据。每个 Server 在内存中存储了一份数据。
(6)Zookeeper 是可以集群复制的,集群间通过 Zab 协议(Zookeeper Atomic Broadcast)来保持数据的一致性;
(7)Zab 协议包含两个阶段:leader election 阶段和 Atomic Brodcast 阶段。
a) 集群中将选举出一个 leader,其他的机器则称为 follower,所有的写操作都被传送给 leader,并通过 brodcast 将所有的更新告诉给 follower。
b) 当 leader 崩溃或者 leader 失去大多数的 follower 时,需要重新选举出一个新的 leader,让所有的服务器都恢复到一个正确的状态。
c) 当 leader 被选举出来,且大多数服务器完成了 和 leader 的状态同步后,leadder election 的过程就结束了,就将会进入到 Atomic brodcast 的过程。
d) Atomic Brodcast 同步 leader 和 follower 之间的信息,保证 leader 和 follower 具有形同的系统状态。
NO3:能说说 Zookeeper 的工作原理?
Zookeeper 的核心是原子广播,这个机制保证了各个 Server 之间的同步。实现这个机制的协议叫做 Zab 协议。
Zab 协议有两种模式,它们 分别是恢复模式(选主)和广播模式(同步)。
Zab 协议 的全称是 Zookeeper Atomic Broadcast** (Zookeeper 原子广播)。Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性。Zab 协议要求每个 Leader 都要经历三个阶段:发现,同步,广播。
当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 Server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 Server 具有相同的系统状态。
为了保证事务的顺序一致性,zookeeper 采用了递增的事务 id 号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加 上了 zxid。实现中 zxid 是一个 64 位的数字,它高 32 位是 epoch 用来标识 leader 关系是否改变,每次一个 leader 被选出来,它都会有一 个新的 epoch,标识当前属于那个 leader 的统治时期。低 32 位用于递增计数。
epoch:可以理解为皇帝的年号,当新的皇帝leader产生后,将有一个新的epoch年号。
每个 Server 在工作过程中有三种状态:
LOOKING:当前 Server 不知道 leader 是谁,正在搜寻。
LEADING:当前 Server 即为选举出来的 leader。
FOLLOWING:leader 已经选举出来,当前 Server 与之同步。
NO4:Zookeeper 为什么要这么设计?
ZooKeeper 设计的目的是提供高性能、高可用、顺序一致性的分布式协调服务、保证数据最终一致性。
高性能(简单的数据模型)
采用树形结构组织数据节点;
全量数据节点,都存储在内存中;
Follower 和 Observer 直接处理非事务请求;
高可用(构建集群)
半数以上机器存活,服务就能正常运行
自动进行 Leader 选举
顺序一致性(事务操作的顺序)
每个事务请求,都会转发给 Leader 处理
每个事务,会分配全局唯一的递增 id(zxid,64 位:epoch + 自增 id)
最终一致性
通过提议投票方式,保证事务提交的可靠性
提议投票方式,只能保证 Client 收到事务提交成功后,半数以上节点能够看到最新数据
NO5:你知道 Zookeeper 中有哪些角色?
系统模型:
领导者(leader)
Leader 服务器为客户端提供读服务和写服务。负责进行投票的发起和决议,更新系统状态。
学习者(learner)
跟随者(
follower
) Follower 服务器为客户端提供读服务,参与 Leader 选举过程,参与写操作“过半写成功”策略。观察者(
observer
) Observer 服务器为客户端提供读服务,不参与 Leader 选举过程,不参与写操作“过半写成功”策略。用于在不影响写性能的前提下提升集群的读性能。
客户端(client)
:服务请求发起方。
NO6:你熟悉 Zookeeper 节点 ZNode 和相关属性吗?
节点有哪些类型?
Znode两种类型
:
持久的(persistent):客户端和服务器端断开连接后,创建的节点不删除(默认)。
短暂的(ephemeral):客户端和服务器端断开连接后,创建的节点自己删除。
Znode有四种形式
:
持久化目录节点(PERSISTENT):客户端与 Zookeeper 断开连接后,该节点依旧存在持久化顺序编号目录节点(PERSISTENT_SEQUENTIAL)
客户端与 Zookeeper 断开连接后,该节点依旧存在,只是 Zookeeper 给该节点名称进行顺序编号:临时目录节点(EPHEMERAL)
客户端与 Zookeeper 断开连接后,该节点被删除:临时顺序编号目录节点(EPHEMERAL_SEQUENTIAL)
客户端与 Zookeeper 断开连接后,该节点被删除,只是 Zookeeper 给该节点名称进行顺序编号
「注意」:创建 ZNode 时设置顺序标识,ZNode 名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护。
节点属性有哪些
一个 znode 节点不仅可以存储数据,还有一些其他特别的属性。接下来我们创建一个/test 节点分析一下它各个属性的含义。
属性说明
NO7:请简述 Zookeeper 的选主流程
Zookeeper 的核心是原子广播,这个机制保证了各个 Server 之间的同步。实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 Server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 Server 具有相同的系统状态。leader 选举是保证分布式数据一致性的关键。
出现选举主要是两种场景:初始化、leader 不可用。
当 zk 集群中的一台服务器出现以下两种情况之一时,就会开始 leader 选举。
(1)服务器初始化启动。
(2)服务器运行期间无法和 leader 保持连接。
而当一台机器进入 leader 选举流程时,当前集群也可能处于以下两种状态。
(1)集群中本来就已经存在一个 leader。
(2)集群中确实不存在 leader。
首先第一种情况,通常是集群中某一台机器启动比较晚,在它启动之前,集群已经正常工作,即已经存在一台 leader 服务器。当该机器试图去选举 leader 时,会被告知当前服务器的 leader 信息,它仅仅需要和 leader 机器建立连接,并进行状态同步即可。
重点是 leader 不可用了,此时的选主制度。
投票信息中包含两个最基本的信息。
sid
:即 server id,用来标识该机器在集群中的机器序号。
zxid
:即 zookeeper 事务 id 号。
ZooKeeper 状态的每一次改变, 都对应着一个递增的 Transaction id,,该 id 称为 zxid.,由于 zxid 的递增性质, 如果 zxid1 小于 zxid2,,那么 zxid1 肯定先于 zxid2 发生。创建任意节点,或者更新任意节点的数据, 或者删除任意节点,都会导致 Zookeeper 状态发生改变,从而导致 zxid 的值增加。
以(sid,zxid)的形式来标识一次投票信息。
例如:如果当前服务器要推举 sid 为 1,zxid 为 8 的服务器成为 leader,那么投票信息可以表示为(1,8)
集群中的每台机器发出自己的投票后,也会接受来自集群中其他机器的投票。每台机器都会根据一定的规则,来处理收到的其他机器的投票,以此来决定是否需要变更自己的投票。
规则如下
:
(1)初始阶段,都会给自己投票。
(2)当接收到来自其他服务器的投票时,都需要将别人的投票和自己的投票进行 pk,规则如下:
优先检查 zxid。zxid 比较大的服务器优先作为 leader。如果 zxid 相同的话,就比较 sid,sid 比较大的服务器作为 leader。
NO8:有了解过 watch 机制吗?
简单地说:client 端会对某个 znode 注册一个 watcher 事件,当该 znode 发生变化时,这些 client 会收到 ZooKeeper 的通知,然后 client 可以根据 znode 变化来做出业务上的改变等。
经典使用场景:zookeeper 为 dubbo 提供服务的注册与发现,作为注册中心,但是大家有没有想过 zookeeper 为啥能够实现服务的注册与发现吗?
这就不得不说一下 zookeeper 的灵魂 Watcher(监听者)。
什么是 watcher?
watcher 是 zooKeeper 中一个非常核心功能 ,客户端 watcher 可以监控节点的数据变化以及它子节点的变化,一旦这些状态发生变化,zooKeeper 服务端就会通知所有在这个节点上设置过 watcher 的客户端 ,从而每个客户端都很快感知,它所监听的节点状态发生变化,而做出对应的逻辑处理。
简单的介绍了一下 watcher ,那么我们来分析一下,zookeeper 是如何实现服务的注册与发现。zookeeper 的服务注册与发现,主要应用的是 zookeeper 的 znode 节点数据模型和 watcher 机制,大致的流程如下:
服务注册:服务提供者(Provider)启动时,会向 zookeeper 服务端注册服务信息,也就是创建一个节点,例如:用户注册服务 com.xxx.user.register,并在节点上存储服务的相关数据(如服务提供者的 ip 地址、端口等)。
服务发现:服务消费者(Consumer)启动时,根据自身配置的依赖服务信息,向 zookeeper 服务端获取注册的服务信息并设置 watch 监听,获取到注册的服务信息之后,将服务提供者的信息缓存在本地,并进行服务的调用。
服务通知:一旦服务提供者因某种原因宕机不再提供服务之后,客户端与 zookeeper 服务端断开连接,zookeeper 服务端上服务提供者对应服务节点会被删除(例如:用户注册服务 com.xxx.user.register),随后 zookeeper 服务端会异步向所有消费用户注册服务 com.xxx.user.register,且设置了 watch 监听的服务消费者发出节点被删除的通知,消费者根据收到的通知拉取最新服务列表,更新本地缓存的服务列表。
上边的过程就是 zookeeper 可以实现服务注册与发现的大致原理。
watcher 有哪些类型?
znode 节点可以设置两类 watch,一种是 DataWatches,基于 znode 节点的数据变更从而触发 watch 事件,触发条件 getData()、exists()、setData()、 create()。
另一种是 Child Watches,基于 znode 的孩子节点发生变更触发的 watch 事件,触发条件 getChildren()、 create()。
而在调用 delete() 方法删除 znode 时,则会同时触发 Data Watches 和 Child Watches,如果被删除的节点还有父节点,则父节点会触发一个 Child Watches。
watcher 有什么特性?
watch 对节点的监听事件是一次性的!客户端在指定的节点设置了监听 watch,一旦该节点数据发生变更通知一次客户端后,客户端对该节点的监听事件就失效了。
如果还要继续监听这个节点,就需要我们在客户端的监听回调中,再次对节点的监听 watch 事件设置为 True。否则客户端只能接收到一次该节点的变更通知。
NO9:那你说说 Zookeeper 有哪些应用场景?
数据发布与订阅
发布与订阅即所谓的配置管理,顾名思义就是将数据发布到 ZooKeeper 节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,地址列表等就非常适合使用。
数据发布/订阅的一个常见的场景是配置中心,发布者把数据发布到 ZooKeeper 的一个或一系列的节点上,供订阅者进行数据订阅,达到动态获取数据的目的。
配置信息一般有几个特点:
数据量小的 KV
数据内容在运行时会发生动态变化
集群机器共享,配置一致
ZooKeeper 采用的是推拉结合的方式。
推: 服务端会推给注册了监控节点的客户端 Wathcer 事件通知
拉: 客户端获得通知后,然后主动到服务端拉取最新的数据
命名服务
作为分布式命名服务,命名服务是指通过指定的名字来获取资源或者服务的地址,利用 ZooKeeper 创建一个全局的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。
统一命名服务的命名结构图如下所示:
1、在分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务。
类似于域名与 IP 之间对应关系,IP 不容易记住,而域名容易记住。
通过名称来获取资源或服务的地址,提供者等信息。
2、按照层次结构组织服务/应用名称。
可将服务名称以及地址信息写到 ZooKeeper 上,客户端通过 ZooKeeper 获取可用服务列表类。
配置管理
程序分布式的部署在不同的机器上,将程序的配置信息放在 ZooKeeper 的 znode 下,当有配置发生改变时,也就是 znode 发生变化时,可以通过改变 zk 中某个目录节点的内容,利用 watch 通知给各个客户端 从而更改配置。
ZooKeeper 配置管理结构图如下所示:
1、分布式环境下,配置文件管理和同步是一个常见问题。
一个集群中,所有节点的配置信息是一致的,比如 Hadoop 集群。
对配置文件修改后,希望能够快速同步到各个节点上。
2、配置管理可交由 ZooKeeper 实现。
可将配置信息写入 ZooKeeper 上的一个 Znode。
各个节点监听这个 Znode。
一旦 Znode 中的数据被修改,ZooKeeper 将通知各个节点。
集群管理
所谓集群管理就是:是否有机器退出和加入、选举 master。
集群管理主要指集群监控和集群控制两个方面。前者侧重于集群运行时的状态的收集,后者则是对集群进行操作与控制。开发和运维中,面对集群,经常有如下需求:
希望知道集群中究竟有多少机器在工作
对集群中的每台机器的运行时状态进行数据收集
对集群中机器进行上下线的操作
集群管理结构图如下所示:
1、分布式环境中,实时掌握每个节点的状态是必要的,可根据节点实时状态做出一些调整。
2、可交由 ZooKeeper 实现。
可将节点信息写入 ZooKeeper 上的一个 Znode。
监听这个 Znode 可获取它的实时状态变化。
3、典型应用
Hbase 中 Master 状态监控与选举。
利用 ZooKeeper 的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建 /currentMaster 节点,最终一定只有一个客户端请求能够创建成功
分布式通知与协调
1、分布式环境中,经常存在一个服务需要知道它所管理的子服务的状态。
a)NameNode 需知道各个 Datanode 的状态。
b)JobTracker 需知道各个 TaskTracker 的状态。
2、心跳检测机制可通过 ZooKeeper 来实现。
3、信息推送可由 ZooKeeper 来实现,ZooKeeper 相当于一个发布/订阅系统。
分布式锁
处于不同节点上不同的服务,它们可能需要顺序的访问一些资源,这里需要一把分布式的锁。
分布式锁具有以下特性:写锁、读锁、时序锁。
写锁:在 zk 上创建的一个临时的无编号的节点。由于是无序编号,在创建时不会自动编号,导致只能客户端有一个客户端得到锁,然后进行写入。
读锁:在 zk 上创建一个临时的有编号的节点,这样即使下次有客户端加入是同时创建相同的节点时,他也会自动编号,也可以获得锁对象,然后对其进行读取。
时序锁:在 zk 上创建的一个临时的有编号的节点根据编号的大小控制锁。
分布式队列
分布式队列分为两种:
1、当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
a)一个 job 由多个 task 组成,只有所有任务完成后,job 才运行完成。
b)可为 job 创建一个/job 目录,然后在该目录下,为每个完成的 task 创建一个临时的 Znode,一旦临时节点数目达到 task 总数,则表明 job 运行完成。
2、队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。
NO10:知道监听器的原理吗?
创建一个 Main()线程。
在 Main()线程中创建两个线程,一个负责网络连接通信(connect),一个负责监听(listener)。
通过 connect 线程将注册的监听事件发送给 Zookeeper。
将注册的监听事件添加到 Zookeeper 的注册监听器列表中。
Zookeeper 监听到有数据或路径发生变化时,把这条消息发送给 Listener 线程。
Listener 线程内部调用 process()方法。
NO11:为什么 Zookeeper 集群的数目,一般为奇数个?
首先需要明确 zookeeper 选举的规则:leader 选举,要求可用节点数量 > 总节点数量/2
。
比如:标记一个写是否成功是要在超过一半节点发送写请求成功时才认为有效。同样,Zookeeper 选择领导者节点也是在超过一半节点同意时才有效。最后,Zookeeper 是否正常是要根据是否超过一半的节点正常才算正常。这是基于 CAP 的一致性原理。
zookeeper 有这样一个特性:集群中只要有过半的机器是正常工作的,那么整个集群对外就是可用的。
也就是说如果有 2 个 zookeeper,那么只要有 1 个死了 zookeeper 就不能用了,因为 1 没有过半,所以 2 个 zookeeper 的死亡容忍度为 0;
同理,要是有 3 个 zookeeper,一个死了,还剩下 2 个正常的,过半了,所以 3 个 zookeeper 的容忍度为 1;
同理:
2->0;两个 zookeeper,最多 0 个 zookeeper 可以不可用。
3->1;三个 zookeeper,最多 1 个 zookeeper 可以不可用。
4->1;四个 zookeeper,最多 1 个 zookeeper 可以不可用。
5->2;五个 zookeeper,最多 2 个 zookeeper 可以不可用。
6->2;两个 zookeeper,最多 0 个 zookeeper 可以不可用。
....
会发现一个规律,2n 和 2n-1 的容忍度是一样的,都是 n-1,所以为了更加高效,何必增加那一个不必要的 zookeeper 呢。
zookeeper 的选举策略也是需要半数以上的节点同意才能当选 leader,如果是偶数节点可能导致票数相同的情况。
总结
很多面试官,面试套路基本就是这个,从背景到原理,到架构体系,再到 Zookeeper 固有特点、最后要求面试者能说出 Zookeeper 的实际应用场景。
「你多学一样本事,就少说一句求人的话。」
推荐阅读
版权声明: 本文为 InfoQ 作者【田维常】的原创文章。
原文链接:【http://xie.infoq.cn/article/783b65774eee7aa9b23a1ed07】。文章转载请联系作者。
评论