面试 - Zookeeper面试题
文章目录
一:什么是ZooKeeper
- 开源的分布式协调服务
- 为分布式应用提供一致性服务的软件
- 可以基于 Zookeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。
- 目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户
- 特性:顺序一致性 & 原子性 & 单一视图 & 可靠性 & 实时性【最终一致性】
- 读请求可以被任意一个处理,写请求要求所有的zk统一之后,才会返回成功,所以机器增高,读的吞吐升高,写的吞吐下降
- 有序性是 zookeeper 中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳zxid
- 读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper 最新的 zxid。
- ZK提供了文件系统和通知机制
二:ZK文件系统和znode
Zookeeper 提供一个多层级的节点命名空间(节点称为 znode)
znode节点都可以设置关联的数据,而文件系统中只有文件节点可以存放数据而目录节点不行。
zk在内存中维护了这个树状的目录结构,这种特性使得 Zookeeper 不能用于存放大量的数据,每个节点的存放数据上限为1M
znode有四种类型:
PERSISTENT
-持久节点 除非手动删除,否则节点一直存在于 Zookeeper 上EPHEMERAL
-临时节点 临时节点的生命周期与客户端会话绑定,一旦客户端会话失效会被移除。PERSISTENT_SEQUENTIAL
-持久顺序节点 基本特性同持久节点,只是增加了顺序属性EPHEMERAL_SEQUENTIAL
-临时顺序节点 基本特性同临时节点,增加了顺序属性
三:如何保证主从节点的状态同步
ZK的核心是原子广播机制,这个机制保证了各个 server 之间的同步
实现这个机制的协议叫做 Zab 协议,Zab 协议有两种模式,它们分别是恢复模式和广播模式。
- 恢复模式 -> 当服务启动或者在领导者崩溃后,Zab就进入了恢复模式
- 当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。
- 状态同步保证了 leader 和 server 具有相同的系统状态。
- 广播模式 -> 一旦 leader 已经和多数的 follower 进行了状态同步后,它就可以开始广播消息了,即进入广播状态。
- 这时候当一个 server 加入 ZooKeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。
- 待到同步结束,它也参与消息广播。
- ZooKeeper 服务一直维持在 Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的 followers 支持
四:Watcher机制【重点】
-
watcher监听注册在znode上,当服务端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher 通知状态和事件类型做出业务上的改变。【也就是所谓的数据变更监听】
-
watcher特性如下:
- 一次性 -> 被触发之后就会被移除,【为什么不做成永久的 -> 如果服务端变动频繁,而监听的客户端很多情况下,每次变动都要通知到所有的客户端,给网络和服务器造成很大压力】
- 串行同步 -> Watcher 回调的过程是一个串行同步的过程
- 轻量 -> 通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容
- 最终一致性 -> 网络延迟,无法保证强一致性
- 可丢失 -> 对于一个未创建的znode的exist watch,如果在客户端断开连接期间被创建了,并且随后在客户端连接上之前又删除了
-
工作机制如下:
- 客户端注册 -> 服务端处理 -> 客户端回调
- 客户端注册 Watcher 实现
- 调用
getData()/getChildren()/exist()
三个 API,传入 Watcher 对象 - 标记请求 request,封装 Watcher 到 WatchRegistration
- 封装成 Packet 对象,向服务端发送 request
- 收到服务端响应后,将 Watcher 注册到 ZKWatcherManager 中进行管理
- 请求返回,完成注册
- 调用
- 服务端处理 Watcher 实现
- 接收 Watcher 并存储 接收到客户端请求,处理请求判断是否需要注册 Watcher
- Watcher 触发,找到对应的Watcher并删除
- 调用 process 方法进行事件通知
- 客户端回调 Watcher 实现
- SendThread 线程接收事件通知,交由 EventThread 线程回调 Watcher
- Watcher 机制同样是一次性的,一旦被触发后,该 Watcher 就失效了
-
当一个客户端连接到一个新的服务器上时,会被以任意会话事件触发。当与一个服务器失去连接的时候,是无法接收到 watch 的
-
当 client 重新连接时,如果需要的话,所有先前注册过的 watch,都会被重新注册。通常这是完全透明的
五:控制和安全
- ACL(Access Control List)访问控制列表,包含三个方面:
- 权限模式
- IP:从 IP 地址粒度进行权限控制
- Digest:最常用,用类似于 username:password 的权限标识来进行权限配置,以区分不同的应用
- World:最开放的权限控制方式,是一种特殊的 digest 模式
- Super:超级用户
- 授权对象:
- 授权对象指的是权限赋予的用户或一个指定实体
- 权限:
- CREATE:数据节点创建权限,允许授权对象在该 Znode 下创建子节点
- DELETE:子节点删除权限,允许授权对象删除该数据节点的子节点
- READ:数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或子节点列表等
- WRITE:数据节点更新权限,允许授权对象对该数据节点进行更新操作
- ADMIN:数据节点管理权限,允许授权对象对该数据节点进行 ACL 相关设置操作
- 权限模式
- Chroot 特性,该特性允许每个客户端为自己设置一个命名空间
- 如果客户端设置了 Chroot,那么该客户端对服务器的任何操作,都将会被限制在其自己的命名空间下
- Chroot能够将一个客户端应用于 Zookeeper 服务端的一颗子树相对应 -> 实现隔离性
六:服务器角色
- Leader -> 事务请求的唯一调度和处理者,保证事务处理的顺序性,各服务的调度者
- Follower -> 处理客户端的非事务请求,转发事务请求给 Leader 服务器,参与事务选举投票,参与 Leader 选举投票
- Observer -> 不影响集群事务处理能力,处理客户端的非事务请求,转发事务请求给 Leader 服务器,不参与任何形式的投票
- 四种工作状态:
- LOOKING:寻找Leader状态,说明此时没有leader,等待选举
- FOLLOWING:跟随者状态。表明当前服务器角色是 Follower。
- LEADING:领导者状态。表明当前服务器角色是 Leader。
- OBSERVING:观察者状态。表明当前服务器角色是 Observer。
七:数据同步
整个集群完成 Leader 选举之后,Learner(Follower 和 Observer 的统称)回向Leader 服务器进行注册。
当 Learner 服务器想 Leader 服务器完成注册后,进入数据同步环节。
- 数据同步流程:(均以消息传递的方式进行)
- Learner 向 Learder 注册 -> 数据同步 -> 同步确认
- Zookeeper 的数据同步通常分为四类:
- 直接差异化同步(DIFF 同步)
- 先回滚再差异化同步(TRUNC+DIFF 同步)
- 仅回滚同步(TRUNC 同步)
- 全量同步(SNAP 同步)
- 在进行数据同步前,Leader服务器会完成数据同步初始化:
- peerLastZxid:从learner服务器注册时发送的ACKEPOCH消息中提取lastZxid(该Learner服务器最后处理的ZXID)
- minCommittedLog:Leader服务器Proposal缓存队列committedLog中最小ZXID
- maxCommittedLog:Leader服务器Proposal缓存队列committedLog中最大ZXID
- 直接差异化同步(DIFF同步)
- 场景:peerLastZxid介于minCommittedLog和maxCommittedLog之间
场景
- 先回滚再差异化同步(TRUNC+DIFF同步)场景:
- 当新的Leader服务器发现某个Learner服务器包含了一条自己没有的事务记录,那么就需要让该Learner服务器进行事务回滚–回滚到Leader服务器上存在的,同时也是最接近于peerLastZxid的ZXID
- 仅回滚同步(TRUNC同步)场景:
- peerLastZxid > maxCommittedLog
- 全量同步(SNAP同步)
- 场景一:peerLastZxid < minCommittedLog
- 场景二:Leader服务器上没有Proposal缓存队列且peerLastZxid不等于lastProcessZxid
八:集群扩容
- ZK有三种部署模式:单机,集群,伪集群
- 集群规则为 2N + 1 台,N>0,单数服务器只要没超过一半的服务器宕机就可以继续使用。
- 3.5开始支持水平扩容,但是支持的不是很好
九:如何保证事务的顺序一致性
zookeeper 采用了全局递增的事务 Id 来标识,所有的 proposal(提议)都在被提出的时候加上了 zxid
zxid 实际上是一个 64 位的数字,高 32 位是 epoch用来标识 leader 周期,如果有新的 leader 产生出来,epoch会自增。低 32 位用来递增计数。
当新产生 proposal 的时候,会依据数据库的两阶段过程,首先会向其他的 server 发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。
十:宕机如何处理:
Zookeeper 本身也是集群,推荐配置不少于 3 个服务器。Zookeeper 自身也要保证当一个节点宕机时,其他节点会继续提供服务。
如果是一个 Follower 宕机,还有 2 台服务器提供访问,因为 Zookeeper 上的数据是有多个副本的,数据并不会丢失;
如果是一个 Leader 宕机,Zookeeper 会选举出新的 Leader。
ZK 集群的机制是只要超过半数的节点正常,集群就能正常提供服务。只有在 ZK节点挂得太多,只剩一半或不到一半节点能工作,集群才失效。
十一:ZAB 和 Paxos 算法的联系与区别
相同点
- 两者都存在一个类似于 Leader 进程的角色,由其负责协调多个 Follower 进程的运行
- Leader 进程都会等待超过半数的 Follower 做出正确的反馈后,才会将一个提案进行提交
- ZAB 协议中,每个 Proposal 中都包含一个 epoch 值来代表当前的 Leader周期,Paxos 中名字为 Ballot
不同点
- ZAB用来构建高可用的分布式数据主备系统(Zookeeper),Paxos是用来构建分布式一致性状态机系统。
十二:ZK应用场景
Zookeeper 是一个典型的发布/订阅模式的分布式数据管理与协调框架,开发人员可以使用它来进行分布式数据的发布和订阅。
- 数据发布/订阅
- 即所谓的配置中心,顾名思义就是发布者发布数据供订阅者进行数据订阅
- 这样可以动态获取数据(配置信息)& 实现数据(配置信息)的集中式管理和数据的动态更新
- push / pull模式
- 数据量通常比较小 & 数据内容在运行时会发生动态更新 & 集群中各机器共享,配置一致
- 实现方式:
- 数据存储:将数据(配置信息)存储到 Zookeeper 上的一个数据节点
- 数据获取:应用在启动初始化节点从 Zookeeper 数据节点读取数据,并在该节点上注册一个数据变更 Watcher
- 数据变更:当变更数据时,更新 Zookeeper 对应节点数据,Zookeeper会将数据变更通知发到各客户端
- 负载均衡
- 命名服务
- 命名服务是指通过指定的名字来获取资源或者服务的地址,利用 zk 创建一个全局的路径
- 这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。
- 分布式协调/通知
- 对于系统调度来说:操作人员发送通知实际是通过控制台改变某个节点的状态,然后 zk 将这些变化发送给注册了这个节点的 watcher 的所有客户端。
- 对于执行情况汇报:每个工作进程都在某个目录下创建一个临时节点。并携带工作的进度数据,这样汇总的进程可以监控目录子节点的变化获得工作进度的实时的全局情况
- 集群管理
- 所谓集群管理无在乎两点:是否有机器退出和加入、选举 master。
- 对于第一点,所有机器约定在父目录下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper 的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。新机器加入也是类似,所有机器收到通知:新兄弟目录加入,highcount 又有了
- 对于第二点,我们稍微改变一下,所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为 master 就好。
- 分布式锁
- 有了 zookeeper 的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。
- 对于第一类,我们将 zookeeper 上的一个 znode 看作是一把锁,通过 createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的 distribute_lock 节点就释放出锁。
- 对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选 master 一样,编号最小的获得锁,用完删除,依次方便
- 分布式队列
- 和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。
- 在特定的目录下创建 PERSISTENT_SEQUENTIAL 节点,创建成功时Watcher 通知等待的队列,队列删除序列号最小的节点用以消费。
- 此场景下Zookeeper 的 znode 用于消息存储,znode 存储的数据就是消息队列中的消息内容,SEQUENTIAL 序列号就是消息的编号,按序取出即可。
- 由于创建的节点是持久化的,所以不必担心队列消息的丢失问题。
十三:ZK和Dubbo的关系
zk是具体的注册中心,dubbo是工具,是手段,是框架
Zookeeper的作用: zookeeper用来注册服务和进行负载均衡,哪一个服务由哪一个机器来提供必需让调用者知道
简单来说就是ip地址和服务名称的对应关系。
当然也可以通过硬编码的方式把这种对应关系在调用方业务代码中实现,但是如果提供服务的机器挂掉调用者无法知晓,如果不更改代码会继续请求挂掉的机器提供服务。
zookeeper通过心跳机制可以检测挂掉的机器并将挂掉机器的ip和服务对应关系从列表中删除。
至于支持高并发,简单来说就是横向扩展,在不更改代码的情况通过添加机器来提高运算能力。
通过添加新的机器向zookeeper注册服务,服务的提供者多了能服务的客户就多了。
dubbo: 是管理中间层的工具,在业务层到数据仓库间有非常多服务的接入和服务提供者需要调度
dubbo提供一个框架解决这个问题。
dubbo只是一个框架,至于你架子上放什么是完全取决于你的,就像一个汽车骨架,你需要配你的轮子引擎。这个框架中要完成调度必须要有一个分布式的注册中心,储存所有服务的元数据,你可以用zk,也可以用别的
十四:为什么集群的数目设置成为奇数
十五:zk的持久化机制
分为快照方式持久化和日志文件方式持久化
快照具体的格式:
日志具体的格式
持久化的详细流程
持久化相关的类主要在包org.apache.zookeeper.server.persistence
下,结构如下:
- TxnLog:接口类型,读取事务性日志的接口
- FileTxnLog:实现TxnLog接口,添加了访问该事务性日志的API
- Snapshot:接口类型,持久层快照接口
- FileSnap:实现了Snapshot接口,负责存储,序列化,反序列化,访问快照
- FileTxnSnapLog:封装了TxnLog & Snapshot,是一个工具类,提供持久化所需的API