面试:Zookeeper常见11个连环炮

試著忘記壹切 提交于 2021-02-12 13:03:04

面试的时候,面试官只要看到你简历的上写的有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设计的目的是提供高性能、高可用、顺序一致性的分布式协调服务、保证数据最终一致性。

高性能(简单的数据模型)

  1. 采用树形结构组织数据节点;
  2. 全量数据节点,都存储在内存中;
  3. Follower 和 Observer 直接处理非事务请求;

高可用(构建集群)

  1. 半数以上机器存活,服务就能正常运行
  2. 自动进行 Leader 选举

顺序一致性(事务操作的顺序)

  1. 每个事务请求,都会转发给 Leader 处理
  2. 每个事务,会分配全局唯一的递增id(zxid,64位:epoch + 自增 id)

最终一致性

  1. 通过提议投票方式,保证事务提交的可靠性
  2. 提议投票方式,只能保证 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节点分析一下它各个属性的含义。

    [zk: localhost:2181(CONNECTED) 6] get /test
    456
    cZxid = 0x59ac //
    ctime = Mon Mar 30 15:20:08 CST 2020
    mZxid = 0x59ad
    mtime = Mon Mar 30 15:22:25 CST 2020
    pZxid = 0x59ac
    cversion = 0
    dataVersion = 2
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 3
    numChildren = 0  

属性说明

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 的一个或一系列的节点上,供订阅者进行数据订阅,达到动态获取数据的目的。

配置信息一般有几个特点:

  1. 数据量小的KV
  2. 数据内容在运行时会发生动态变化
  3. 集群机器共享,配置一致

ZooKeeper 采用的是推拉结合的方式。

  1. 推: 服务端会推给注册了监控节点的客户端 Wathcer 事件通知
  2. 拉: 客户端获得通知后,然后主动到服务端拉取最新的数据
命名服务

作为分布式命名服务,命名服务是指通过指定的名字来获取资源或者服务的地址,利用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. 对集群中的每台机器的运行时状态进行数据收集
  3. 对集群中机器进行上下线的操作

集群管理结构图如下所示:

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:知道监听器的原理吗?

  1. 创建一个Main()线程。
  2. 在Main()线程中创建两个线程,一个负责网络连接通信(connect),一个负责监听(listener)。
  3. 通过connect线程将注册的监听事件发送给Zookeeper。
  4. 将注册的监听事件添加到Zookeeper的注册监听器列表中。
  5. Zookeeper监听到有数据或路径发生变化时,把这条消息发送给Listener线程。
  6. 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的实际应用场景。

「你多学一样本事,就少说一句求人的话。」

本文分享自微信公众号 - 故里学Java(WLQ171223)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!