Zookeeper 面试题
Zookeeper 是一个开源的分布式协 调服务项目,由 Apache 开发,专门为分布式应用提供协调服务。
从设计模式的角度来看,Zookeeper 是一个基于 观察者模式 设计的分布式服务管理框架。它负责存储和管理分布式系统中大家都关心的数据,并接受观察者的注册。一旦数据的状态发生变化,Zookeeper 会通知已经在 Zookeeper 上注册的观察者,使其做出相应的反应。
Zookeeper 提供的服务
- 统一命名服务
- 统一配置管理
- 统一集群管理
- 服务器节点动态上下线
- 软负载均衡
Zookeeper 致力于为高吞吐量的大型分布式系统提供高性能、高可用性,并且具备严格顺序访问控制能力的分布式协调服务。
Zookeeper 的特性
-
顺序一致性:
从一个客户端发起的事务请求,最终会严格按照其发起顺序被应用到 Zookeeper 中。对于来自客户端的每个更新请求,Zookeeper 都会分配一个全局唯一的递增 ID (zxid),这个 ID 反映了所有事务请求的先后顺序。 -
原子性:
所有事务请求的处理结果在整个集群中的所有机器上都是一致的。 -
最终一致性:
所有客户端看到的服务端数据模型都是一致的。 -
可靠性:
一旦服务端成功应用了一个事务,则其引起的改变会一直保留,直到被另外一个事务所更改。如果消息被一台服务器接受,那么它将被所有的服务器接受。 -
实时性:
一旦一个事务被成功应用,Zookeeper 可以保证客户端立即可以读取到该事务变更后的最新状态数据。 -
等待无关 (wait-free):
慢的或失效的客户端不会干预快速客户端的请求,确保每个客户端都能有效地等待。
性能表现
由于 Zookeeper 的所有更新和删除操作都是基于事务的,因此在读多写少的应用场景中,它表现出极高的性能。
Zookeeper 将数据全量存储在内存中,以保持高性能,并通过服务集群来实现高可用性。
角色介绍
- Leader(领导者):负责进行投票的发起和决议,更新系统状态,Leader 是由选举产生;
- Follower(跟随者): 用于接受客户端请求并向客户端返回结果,在选主过程中参与投票;
- Observer(观察者):可以接受客户端连接,接受读写请求,写请求转发给 Leader,但 Observer 不参加投票过程,只同步 Leader 的状态,Observer 的目的是为了扩展系统,提高读取速度。
- Clinet(客户端):连接Zookeeper集群的使用者,请求的发起者,独立于Zookeeper集群的角色。
ZooKeeper 架构
在 ZooKeeper 的架构图中,我们需要掌握以下几个关键点:
-
服务器端(Server)和客户端(Client):
- ZooKeeper 分为服务器端和客户端。客户端可以连接到整个 ZooKeeper 服务中的任意服务器(除非
leaderServes
参数被显式设置,此时 leader 不允许接受客户端连接)。
- ZooKeeper 分为服务器端和客户端。客户端可以连接到整个 ZooKeeper 服务中的任意服务器(除非
-
客户端与服务器的连接:
- 客户端使用并维护一个 TCP 连接,通过这个连接发送请求、接收响应、获取观察事件以及发送心跳信号。如果这个 TCP 连接中断,客户端会自动尝试连接到另一台 ZooKeeper 服务器。客户端首次连接到 ZooKeeper 服务时,接收连接的服务器会为该客户端建立一个会话。当客户端连接到另一台服务器时,会话会在新的服务器上重新建立。
-
ZooKeeper 集群:
- 每个 Server 代表一台安装了 ZooKeeper 服务的机器,这些机器共同组成了 ZooKeeper 服务的集群(可以是一个真实集群或伪集群)。
-
集群的容错性:
- 组成 ZooKeeper 服务的服务器彼此互知。它们维护着内存中的状态图像,并将事务日志和快照存储在持久存储中。只要集群中的大多数服务器可用,ZooKeeper 服务就能保持可用状态。
-
Leader 选举与数据更新:
- ZooKeeper 启动时,会从集群中选举出一个 Leader,负责处理数据更新操作。一个更新操 作只有在大多数服务器成功修改数据时才算成功。每个 Server 在内存中都存储了一份数据副本。
-
集群复制与数据一致性:
- ZooKeeper 支持集群复制,集群间通过 Zab 协议(Zookeeper Atomic Broadcast)来保持数据一致性。
-
Zab 协议的两个阶段:
- 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具有形同的系统状态。
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与之同步。
Zookeeper为什么要这么设计?
ZooKeeper设计的目的是提供高性能、高可用、顺序一致性的分布式协调服务、保证数据最终一致性。
高性能(简单的数据模型)
- 采用树形结构组织数据节点;
- 全量数据节点,都存储在内存中;
- Follower 和 Observer 直接处理非事务请求;
高可用(构建集群)
- 半数以上机器存活,服务就能正常运行
- 自动进行 Leader 选举
顺序一致性(事务操作的顺序)
- 每个事务请求,都会转发给 Leader 处理
- 每个事务,会分配全局唯一的递增id(zxid,64位:epoch + 自增 id)
最终一致性
- 通过提议投票方式,保证事务提交的可靠性
- 提议投票方式,只能保证 Client 收到事务提交成功后,半数以上节点能够看到最新数据
Zookeeper节点ZNode和相关属性
Znode两种类型:
- 持久的(persistent):客户端和服务器端断开连接后,创建的节点不删除(默认)。
- 短暂的(ephemeral):客户端和服务器端断开连接后,创建的节点自己删除。
Znode有四种形式
- 持久化目录节点(PERSISTENT):客户端与Zookeeper断开连接后,该节点依旧存在 持久化顺序编号目录节点(PERSISTENT_SEQUENTIAL)
- 客户端与Zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号:临时目录节点(EPHEMERAL)
- 客户端与Zookeeper断开连接后,该节点被删除:临时顺序编号目录节点(EPHEMERAL_SEQUENTIAL)
- 客户端与Zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
创建ZNode时设置顺序标识,ZNode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护。
Zookeeper的选主流程
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就 结束了。状态同步保证了leader和Server具有相同的系统状态。leader选举是保证分布式数据一致性的关键。
出现选举主要是两种场景:初始化、leader不可用。
当zk集群中的一台服务器出现以下两种情况之一时,就会开始leader选举。
- 服务器初始化启动。
- 服务器运行期间无法和leader保持连接。
而当一台机器进入leader选举流程时,当前集群也可能处于以下两种状态。
- 集群中本来就已经存在一个leader。
- 集群中确实不存在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)
集群中的每台机器发出自己的投票后,也会接受来自集群中其他机器的投票。每台机器都会根据一定的规则,来处理收到的其他机器的投票,以此来决定是否需要变更自己的投票。
规则如 下:
- 初始阶段,都会给自己投票。
- 当接收到来自其他服务器的投票时,都需要将别人的投票和自己的投票进行pk,规则如下:
优先检查zxid。zxid比较大的服务器优先作为leader。如果zxid相同的话,就比较sid,sid比较大的服务器作为leader。
watch机制
简单地说:client端会对某个znode 注册一个watcher事件,当该znode发生变化时,这些client会收到ZooKeeper的通知,然后client可以根据znode变化来做出业务上的改变等。
经典使用场景:zookeeper为dubbo提供服务的注册与发现,作为注册中心,但是大家有没有想过zookeeper为啥能够实现服务的注册与发现吗?
什么是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。否则客户端只能接收到一次该节点的变更通知。
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方式进行入队和出队操作,例如实现生产者和消费者模型。
知道监听器的原理吗?
- 创建一个Main()线程。
- 在Main()线程中创建两个线程,一个负责网络连接通信(connect),一个负责监听(listener)。
- 通过connect线程将注册的监听事件发送给Zookeeper。
- 将注册的监听事件添加到Zookeeper的注册监听器列表中。
- Zookeeper监听到有数据或路径发生变化时,把这条消息发送给Listener线程。
- Listener线程内部调用process()方法。