面试题总结-Zookeeper

  • 1. ZooKeeper是什么
  • 2.zookeeper的三种部署模式
  • 3.Zookeeper 怎么保证主从节点的状态同步?
  • 恢复模式
  • 广播模式
  • 4.集群中为什么有主节点
  • 5.Zookeeper为什么能用做注册中心?
  • 6.ZooKeeper 支持哪些数据节点类型?
  • 7.zookeeper集群中服务器的角色和状态
  • 8.Zookeeper快速领导者选举原理
  • 9.Zookeeper集群节点数量为什么要是奇数个?
  • 10.Zookeeper可以做什么?
  • 11.Zookeeper实现分布式锁


1. ZooKeeper是什么

ZooKeeper由雅虎研究院开发,是Google Chubby的开源实现,后来托管到Apache,于2010年11月正式成为Apache的顶级项目。
ZooKeeper是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务。

分布式应用程序可以基于ZooKeeper实现数据发布与订阅、负载均衡、命名服务、分布式协调与通知、集群管理、Leader选举、分布式锁、分布式队列等功能。

2.zookeeper的三种部署模式

Zookeeper 有三种部署模式分别是单机模式、伪集群模式、集群模式。这三种模式在不同的场景下使用:

  • 单机部署:一般用来检验 Zookeeper 基础功能,熟悉 Zookeeper 各种基础操作及特性。
  • 伪集群部署:在单台机器上部署集群,方便在本地验证集群模式下的各种功能。
  • 集群部署:一般在生产环境使用,具备一致性、分区容错性。

3.Zookeeper 怎么保证主从节点的状态同步?

Zookeeper 的核心是原子广播机制,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式和广播模式。

恢复模式

  • 当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 server 具有相同的系统状态。

广播模式

  • 一旦 leader 已经和多数的 follower 进行了状态同步后,它就可以开始广播消息了,即进入广播状态。这时候当一个 server 加入 ZooKeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。待到同步结束,它也参与消息广播。ZooKeeper 服务一直维持在 Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的 followers 支持。

4.集群中为什么有主节点

在分布式环境中,有些业务逻辑只需要集群中的某一台机器进行执行,其他的机器可以共享这个结果,这样可以大大减少重复计算,提高性能,于是就需要进行 leader 选举。

5.Zookeeper为什么能用做注册中心?

zookeeper就是个分布式文件系统,当你注册服务就是创建了一个znode节点,节点存储了该服务的IP、端口、调用方式。
第一次调用服务,定位服务位置,缓存到本地。再次调用通过负载均衡算法从IP列表中取一个服务提供者的服务器调用服务。
当服务器下线,这个服务也就下线了。再次上线就会缓存新服务ip等信息。

从CP 模型上来讲,zookeeper 并不适合注册中心高可用的需要。
从性能上来讲,zookeeper 也无法满足注册中心大规模且频繁注册写的场景。
zookeeper 的特长是做分布式协调服务,例如 kafka、hbase、flink、hadoop 等大项目都在用 zookeeper,用的挺好的,因为是用对了地方。

6.ZooKeeper 支持哪些数据节点类型?

1. PERSISTEN - 持久节点

  • 除非手动删除,否则节点一直存在于 ZooKeeper 上。

2. EPHEMERAL - 临时节点

  • 临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与 ZooKeeper 连接断开不一定会话失效),那么这个客户端创建的所有临时节点都会被移除。

3. PERSISTENT _ SEQUENTIAL - 持久顺序节点

  • 基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。

4. EPHEMERAL _ SEQUENTIAL - 临时顺序节点

  • 基本特性同临时节点,增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。

7.zookeeper集群中服务器的角色和状态

zk服务:不同角色执行不同的任务。
在区分zk服务器角色前,先解释几个概念:

  • 事务请求:
    在zk中,会改变服务器状态的请求称为事务请求(创建节点、更新数据、删除节点、创建会话等等)
  • 非事务请求
    从zk读取数据但是不对状态进行任何修改的请求称为非事务请求
  • Leader角色
    Leader服务器是zk集群工作的核心,主要工作有两个:
    事务请求的唯一调度者和处理者,保证集群事务处理的顺序性。
    集群内部各个服务器的调度者
  • Follower角色
    Follower是zk集群的跟随者,主要工作有三个:
    处理客户端非事务性请求,转发事务请求给Leader服务器(事务请求都由Leader处理)
    参与事务请求Proposal的投票
    参与Leader选举投票
  • Observer角色
    Observer充当观察者角色,观察zk集群的最新状态变化并将这些状态同步过来,对于非事务请求可以进行独立的处理,对于事务请求,则会转发给Leader服务器进行处理,Observer不会参与任何形式的投票,包括事务请求Proposal的投票和Leader选举的投票。

服务器具有四种状态,分别是LOOKING,FOLLOWING,LEADING,OBSERVING

  • LOOKING
    寻找leader状态
    当前服务器处于该状态时,它会认为当前集群中没有leader,因此需要进入leader选举状态。
  • FOLLOWING
    跟随者状态
    表示当前服务器的角色是Follower角色
  • LEADING
    领导者状态
    表示当前服务器是Leader
  • OBSERVING
    观察者状态
    表示当前服务器角色是Observer

8.Zookeeper快速领导者选举原理

  • 个人能力:Zookeeper是一个数据库,集群中节点的数据越新就代表此节点能力越强,而在Zookeeper中可以通事务id(zxid)来表示数据的新旧,一个节点最新的zxid越大则该节点的数据越新。所以Zookeeper选举时会根据zxid的大小来作为投票的基本规则。
  • 改票:Zookeeper集群中的某一个节点在开始进行选举时,首先认为自己的数据是最新的,会先投自己一票,并且把这张选票发送给其他服务器,这张选票里包含了两个重要信息:zxid和sid,sid表示这张选票投的服务器id,zxid表示这张选票投的服务器上最大的事务id,同时也会接收到其他服务器的选票,接收到其他服务器的选票后,可以根据选票信息中的zxid来与自己当前所投的服务器上的最大zxid来进行比较,如果其他服务器的选票中的zxid较大,则表示自己当前所投的机器数据没有接收到的选票所投的服务器上的数据新,所以本节点需要改票,改成投给和刚刚接收到的选票一样。
  • 投票箱:Zookeeper集群中会有很多节点,和人类选举不一样,Zookeeper集群并不会单独去维护一个投票箱应用,而是在每个节点内存里利用一个数组来作为投票箱。每个节点里都有一个投票箱,节点会将自己的选票以及从其他服务器接收到的选票放在这个投票箱中。因为集群节点是相互交互的,并且选票的PK规则是一致的,所以每个节点里的这个投票箱所存储的选票都会是一样的,这样也可以达到公用一个投票箱的目的。
  • 领导者:Zookeeper集群中的每个节点,开始进行领导选举后,会不断的接收其他节点的选票,然后进行选票PK,将自己的选票修改为投给数据最新的节点,这样就保证了,每个节点自己的选票代表的都是自己暂时所认为的数据最新的节点,再因为其他服务器的选票都会存储在投票箱内,所以可以根据投票箱里去统计是否有超过一半的选票和自己选择的是同一个节点,都认为这个节点的数据最新,一旦整个集群里超过一半的节点都认为某一个节点上的数据最新,则该节点就是领导者。

9.Zookeeper集群节点数量为什么要是奇数个?

首先需要明确zookeeper选举的规则:leader选举,要求可用节点数量 > 总节点数量/2 。
采用奇数个的节点主要是出于两方面的考虑:

  • 防止由脑裂造成的集群不可用。
    在节点数量是奇数个的情况下, zookeeper集群总能对外提供服务(即使损失了一部分节点);如果节点数量是偶数个,会存在zookeeper集群不能用的可能性(脑裂成两个均等的子集群的时候)。
    在生产环境中,如果zookeeper集群不能提供服务,那将是致命的 , 所以zookeeper集群的节点数一般采用奇数个。
  • 在容错能力相同的情况下,奇数台更节省资源。
    三个节点允许一个节点宕机,四个节点同样允许一个节点宕机
    在相同容错能力的情况下,本着节约资源的原则,zookeeper集群的节点数维持奇数个更好一些

10.Zookeeper可以做什么?

  • 配置管理:在我们的应用中除了代码外,还有一些就是各种配置。比如数据库连接等。一般我们都是使用配置文件的方式,在代码中引入这些配置文件。但是当我们只有一种配置,只有一台服务器,并且不经常修改的时候,使用配置文件是一个很好的做法,但是如果我们配置非常多,有很多服务器都需要这个配置,而且还可能是动态的话使用配置文件就不是个好主意了。这个时候往往需要寻找一种集中管理配置的方法,我们在这个集中的地方修改了配置,所有对这个配置感兴趣的都可以获得变更。比如我们可以把配置放在数据库里,然后所有需要配置的服务都去这个数据库读取配置。但是,因为很多服务的正常运行都非常依赖这个配置,所以需要这个集中提供配置服务的服务具备很高的可靠性。一般我们可以用一个集群来提供这个配置服务,但是用集群提升可靠性,那如何保证配置在集群中的一致性呢? 这个时候就需要使用一种实现了一致性协议的服务了。ZooKeeper就是这种服务,它使用Zab这种一致性协议来提供一致性。现在有很多开源项目使用ZooKeeper来维护配置,比如在HBase中,客户端就是连接一个ZooKeeper,获得必要的HBase集群的配置信息,然后才可以进一步操作。还有在开源的消息队列Kafka中,也使用ZooKeeper来维护broker的信息。在Alibaba开源的SOA框架Dubbo中也广泛的使用ZooKeeper管理一些配置来实现服务治理。
  • 名字服务:名字服务这个就很好理解了。比如为了通过网络访问一个系统,我们得知道对方的IP地址,但是IP地址对人非常不友好,这个时候我们就需要使用域名来访问。但是计算机是不能是别域名的。怎么办呢?如果我们每台机器里都备有一份域名到IP地址的映射,这个倒是能解决一部分问题,但是如果域名对应的IP发生变化了又该怎么办呢?于是我们有了DNS这个东西。我们只需要访问一个大家熟知的(known)的点,它就会告诉你这个域名对应的IP是什么。在我们的应用中也会存在很多这类问题,特别是在我们的服务特别多的时候,如果我们在本地保存服务的地址的时候将非常不方便,但是如果我们只需要访问一个大家都熟知的访问点,这里提供统一的入口,那么维护起来将方便得多了。
  • 分布式锁:可以利用ZooKeeper来协调多个分布式进程之间的活动。比如在一个分布式环境中,为了提高可靠性,我们的集群的每台服务器上都部署着同样的服务。但是,一件事情如果集群中的每个服务器都进行的话,那相互之间就要协调,编程起来将非常复杂。而如果我们只让一个服务进行操作,那又存在单点。通常还有一种做法就是使用分布式锁,在某个时刻只让一个服务去干活,当这台服务出问题的时候锁释放,立即fail over到另外的服务。这在很多分布式系统中都是这么做,这种设计有一个更好听的名字叫Leader Election(leader选举)。比如HBase的Master就是采用这种机制。但要注意的是分布式锁跟同一个进程的锁还是有区别的,所以使用的时候要比同一个进程里的锁更谨慎的使用。
  • 集群管理:在分布式的集群中,经常会由于各种原因,比如硬件故障,软件故障,网络问题,有些节点会进进出出。有新的节点加入进来,也有老的节点退出集群。这个时候,集群中其他机器需要感知到这种变化,然后根据这种变化做出对应的决策。比如我们是一个分布式存储系统,有一个中央控制节点负责存储的分配,当有新的存储进来的时候我们要根据现在集群目前的状态来分配存储节点。这个时候我们就需要动态感知到集群目前的状态。还有,比如一个分布式的SOA架构中,服务是一个集群提供的,当消费者访问某个服务时,就需要采用某种机制发现现在有哪些节点可以提供该服务(这也称之为服务发现,比如Alibaba开源的SOA框架Dubbo就采用了ZooKeeper作为服务发现的底层机制)。还有开源的Kafka队列就采用了ZooKeeper作为Cosnumer的上下线管理。

11.Zookeeper实现分布式锁

基于临时顺序节点实现(推荐)
锁分为两种:共享锁(读锁)和排他锁(写锁)

  • 读锁:当有一个线程获取读锁后,其他线程也可以获取读锁,但是在读锁没有完全被释放之前,其他线程不能获取写锁。
  • 写锁:当有一个线程获取写锁后,其他线程就无法获取读锁和写锁了。

zookeeper有一种节点类型叫做临时序号节点,它会按序号自增地创建临时节点,这正好可以作为分布式锁的实现工具。

  • 读锁获取原理:
    1、根据资源的id创建临时序号节点:/lock/mylockR0000000005 Read
    2、获取/lock下的所有子节点,判断比他小的节点是否全是读锁,如果是读锁则获取锁成功
    3、如果不是,则阻塞等待,监听自己的前一个节点。
    4、当前面一个节点发生变更时,重新执行第二步操作。
  • 写锁获取原理:
    1、根据资源的id创建临时序号节点:/lock/mylockW0000000006 Write
    2、获取 /lock 下所有子节点,判断最小的节点是否为自己,如果是则获锁成功
    3、如果不是,则阻塞等待,监听自己的前一个节点
    4、当前面一个节点发生变更时,重新执行第二步。