ZooKeeper 的功能和原理

筅森魡賤 提交于 2019-11-27 01:36:30

最近在学习 ZooKeeper  的相关知识,做个笔记,供以后学习和查询使用。

ZooKeeper  是一个开源的分布式协调服务,由雅虎创建,是 Google Chubby 的开源实现。分布式应用程序可以基于 ZooKeeper  实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、配置维护,名字服务、分布式同步、分布式锁和分布式队列等功能。

一、基本概念

在ZooKeeper 中存在一些基本概念,下面逐一介绍:

(1)集群角色

Leader 角色:Leader 服务器是整个ZooKeeper集群的核心,主要的工作任务有两项:

  • 事物请求的唯一调度和处理者,保证集群事物处理的顺序性。
  • 集群内部各服务器的调度者。

Follower 角色主要职责是:

  • 处理客户端非事物请求、转发事物请求给 leader 服务器。
  • 参与事物请求 Proposal 的投票(Leader 发起的提案,要求 Follower 投票,需要半数以上follower节点通过,leader才会commit数据。)
  • 参与 Leader 选举的投票。

Observer 角色

Observer 是 ZooKeeper 3.3 开始引入的一个全新的服务器角色,该角色充当了观察者的角色。观察ZooKeeper集群中的最新状态变化并将这些状态变化同步到 observer 服务器上。Observer 的工作原理与 follower 角色基本一致,而它和 follower 角色唯一的不同在于 observer 不参与任何形式的投票,包括事务请求Proposal的投票和leader选举的投票。简单来说,observer服务器只提供非事物请求服务,通常在于不影响集群事务处理能力的前提下提升集群非事物处理的能力。

ZooKeeper  默认只有 Leader 和 Follower 两种角色,没有 Observer 角色。为了使用 Observer 模式,在任何想变成Observer的节点的配置文件中加入:peerType=observer 并在所有 server 的配置文件中,配置成 observer 模式的 server 的那行配置追加 :observer

(2)节点读写分工

1-ZooKeeper群的所有机器通过一个 Leader 选举过程来选定一台被称为“Leader”的机器,Leader服务器为客户端提供读和写服务。

2-Follower 和 Observer 都能提供读服务,不能提供写服务。两者唯一的区别在于,Observer机器不参与 Leader 选举过程,也不参与写操作的『过半写成功』策略,因此 Observer 可以在不影响写性能的情况下提升集群的读性能。

(3)Session

Session 是指客户端会话,在讲解客户端会话之前,我们先来了解下客户端连接。在 ZooKeeper 中,一个客户端连接是指客户端和ZooKeeper 服务器之间的TCP长连接。ZooKeeper对外的服务端口默认是2181,客户端启动时,首先会与服务器建立一个TCP连接,从第一次连接建立开始,客户端会话的生命周期也开始了,通过这个连接,客户端能够通过心跳检测和服务器保持有效的会话,也能够向 ZooKeeper  服务器发送请求并接受响应,同时还能通过该连接接收来自服务器的 Watch 事件通知。

Session 的 SessionTimeout 值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在SessionTimeout 规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。

(4)数据节点

ZooKeeper 的结构其实就是一个树形结构,leader就相当于其中的根结点,其它节点就相当于follow节点,每个节点都保留自己的内容。

ZooKeeper 的节点分两类:持久节点和临时节点

     - 持久节点:所谓持久节点是指一旦这个 树形结构上被创建了,除非主动进行对树节点的移除操作,否则这个 节点将一直保存在ZooKeeper上。

     - 临时节点:临时节点的生命周期跟客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。

(5)节点状态

在ZooKeeper中的节点存在三种状态:

following:当前节点是跟随者,服从 leader 节点的命令。

leading:当前节点是 leader,负责协调事务。

election/locking:节点处于选举状态,恢复模式。

(6)事物操作

在ZooKeeper 中,能改变ZooKeeper 服务器状态的操作称为事务操作。一般包括数据节点创建与删除、数据内容更新和客户端会话创建与失效等操作。对应每一个事务请求,ZooKeeper都会为其分配一个全局唯一的事务ID,用 ZXID 表示,通常是一个64位的数字。每一个 ZXID对应一次更新操作,从这些 ZXID 中可以间接地识别出 ZooKeeper  处理这些事务操作请求的全局顺序。

Zxid 是一个 64 位的数字,其中低 32 位是一个简单的单调递增的计数器,针对客户端每一个事务请求,计数器加 1;而高 32 位则代表 Leader 周期 epoch 的编号,每个当选产生一个新的 Leader 服务器,就会从这个 Leader 服务器上取出其本地日志中最大事务的ZXID,并从中读取 epoch 值,然后加 1,以此作为新的 epoch,并将低 32 位从 0 开始计数。

(7)Watch监听器

Watch是 ZooKeeper  中一个很重要的特性。ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper  服务端会将事件通知到感兴趣的客户端上去。该机制是 ZooKeeper  实现分布式协调服务的重要特性。

二、ZooKeeper 集群

基本架构:

1、每个Server在内存中存储了一份数据;

2、ZooKeeper 启动时,将从实例中选举一个leader(Paxos协议);

3、Leader负责处理数据更新等操作(Zab协议);

4、一个更新操作成功,当且仅当大多数Server在内存中成功修改

关于集群部署的个数原理

通常 ZooKeeper  是由 2n+1 台 server 组成,每个 server 都知道彼此的存在。对于 2n+1 台 server,只要有 n+1 台(大多数)server 可用,整个系统保持可用。

我们已经了解到,一个 ZooKeeper  集群如果要对外提供可用的服务,那么集群中必须要有过半的机器正常工作并且彼此之间能够正常通信,基于这个特性,如果向搭建一个能够允许 F 台机器down 掉的集群,那么就要部署 2*F+1 台服务器构成的ZooKeeper  集群。因此 3 台机器构成的 ZooKeeper  集群,能够在挂掉一台机器后依然正常工作。一个 5 台机器集群的服务,能够对 2 台机器怪调的情况下进行容灾。如果一台由 6 台服务构成的集群,同样只能挂掉 2 台机器。因此, 5 台和 6 台在容灾能力上并没有明显优势,反而增加了网络通信负担。系统启动时,集群中的 server 会选举出一台server 为 Leader,其它的就作为 follower(这里先不考虑observer 角色)。

总之,之所以要满足这样一个等式,是因为一个节点要成为集群中的 leader,需要有超过及群众过半数的节点支持,这个涉及到 leader 选举算法。同时也涉及到事务请求的提交投票。

三、ZAB协议

ZAB(ZooKeeper  Atomic Broadcast)是为ZooKeeper 设计的一种支持崩溃恢复的原子广播协议,核心是定义了那些会变更zk服务器数据状态的事务请求的处理方式。基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。

此协议解决最主要的两个问题:

1-集群中,leader奔溃了,数据一致性怎么保证?

2-选举leader时,整个集群无法处理写操作,如何快速选举和保持一致性。

ZAB协议最主要的功能:

1-消息广播

2-leader选举(快速选举算法 fastLeaderelection,集群刚启动时,leader还未启动和集群中超过一半节点断连)

3-leader重新选举后,数据如何同步一致性。

ZAB 协议包含两种基本模式,分别是:崩溃恢复和原子广播。

1)原子广播

过程:

1-leader接收到消息请求后,将消息赋予一个全局唯一的64位自增id,叫:zxid,通过zxid的大小比较就可以实现因果有序这个特征。

2-leader为每个follower准备了一个FIFO队列(通过TCP协议来实现,以实现全局有序这一个特点)将带有zxid的消息作为一个提案(proposal)分发给所有的 follower

3-follower接收到proposal,先把proposal写到磁盘,写入成功以后再向leader回复一个ack

4-leader接收到合法数量(超过半数节点)的ack后,leader就会向这些follower发送commit命令,同时会在本地执行该消息。

5-follower收到消息的commit命令以后,会提交该消息

2)崩溃恢复

当整个集群在启动时,或者当 leader 节点出现网络中断、崩溃等情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader,当 leader 服务器选举出来后,并且集群中有过半的机器和该 leader 节点完成数据同步后(同步指的是数据同步,用来保证集群中过半的机器能够和 leader 服务器的数据状态保持一致),ZAB 协议就会退出恢复模式。

当集群中已经有过半的 Follower 节点完成了和 Leader 状态同步以后,那么整个集群就进入了消息广播模式。这个时候,在 Leader 节点正常工作时,启动一台新的服务器加入到集群,那这个服务器会直接进入数据恢复模式,和leader 节点进行数据同步。同步完成后即可正常对外提供非事务请求的处理。

ZAB 协议这个基于原子广播协议的消息广播过程,在正常情况下是没有任何问题的,但是一旦 Leader 节点崩溃,或者由于网络问题导致 Leader 服务器失去了过半的Follower 节点的联系(leader 失去与过半 follower 节点联系,可能是 leader 节点和 follower 节点之间产生了网络分区(脑裂),那么此时的 leader 不再是合法的 leader 了),那么就会进入到崩溃恢复模式。在 ZAB 协议中,为了保证程序的正确运行,整个恢复过程结束后需要选举出一个新的Leader 为了使 leader 挂了后系统能正常工作,需要解决以下两个问题 :

a-已经被处理的消息不能丢失

当 leader 收到合法数量 follower 的 ACKs 后,就向各个 follower 广播 COMMIT 命令,同时也会在本地执行 COMMIT 并向连接的客户端返回「成功」。但是如果在各个 follower 在收到 COMMIT 命令前 leader就挂了,导致剩下的服务器并没有执行都这条消息。

 

 

例如:leader 对事务消息发起 commit 操作,但是该消息在follower1 上执行了,但是 follower2 还没有收到 commit,就已经挂了,而实际上客户端已经收到该事务消息处理成功的回执了。所以在 zab 协议下需要保证所有机器都要执行这个事务消息

b-被丢弃的消息不能再次出现

当 leader 接收到消息请求生成 proposal 后就挂了,其他 follower 并没有收到此 proposal,因此经过恢复模式重新选了 leader 后,这条消息是被跳过的。 此时,之前挂了的 leader 重新启动并注册成了 follower,他保留了被跳过消息的 proposal 状态,与整个系统的状态是不一致的,需要将其删除。

针对崩溃恢复的两种情况分析

ZAB协议需要满足上面两种情况,就必须要设计一个leader选举算法,能够确保已经被leader提交的事务Proposal能够提交、同时丢弃已经被跳过的事务Proposal。 

针对这个要求:

如果leader选举算法能够保证新选举出来的Leader服务器拥有集群中所有机器最高编号(ZXID 最大)的事务Proposal,那么就可以保证这个新选举出来的leader一定具有已经提交的提案。因为所有提案被commit之前必须有超过半数的follower ack,即必须有超过半数节点的服务器的事务日志上有该提案的proposal,因此只要有合法数量的节点正常工作,就必然有一个节点保存了所有被commit消息的proposal状态。

另外一个,zxid是64位,高32位是epoch编号,每经过一次Leader选举产生一个新的leader,新的leader会将epoch号+1,低32位是消息计数器,每接收到一条消息这个值+1,新leader选举后这个值重置为0。这样设计的好处在于老的leader挂了以后重启,它不会被选举为leader,因此此时它的zxid肯定小于当前新的leader。当老的leader作为follower接入新的leader后,新的leader会让它将所有的拥有旧的epoch号的未被commit的proposal清除。

四、典型应用场景

ZooKeeper  是一个高可用的分布式数据管理与协调框架。基于对ZAB算法的实现,该框架能够很好地保证分布式环境中数据的一致性。也是基于这样的特性,使得 ZooKeeper  成为了解决分布式一致性问题的利器。

1、数据发布与订阅(配置中心)

  数据发布与订阅,即所谓的配置中心,顾名思义就是发布者将数据发布到 ZooKeeper  节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和动态更新。

    对于:数据量通常比较小。数据内容在运行时动态变化。集群中各机器共享,配置一致。

这样的全局配置信息就可以发布到 ZooKeeper 上,让客户端(集群的机器)去订阅该消息。

发布/订阅系统一般有两种设计模式,分别是推(Push)和拉(Pull)模式。

      - 推模式

          服务端主动将数据更新发送给所有订阅的客户端

      - 拉模式

          客户端主动发起请求来获取最新数据,通常客户端都采用定时轮询拉取的方式

ZooKeeper  采用的是推拉相结合的方式:

    客户端想服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知后,需要主动到服务端获取最新的数据

2、命名服务

    命名服务也是分布式系统中比较常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务,远程对象等等——这些我们都可以统称他们为名字。

其中较为常见的就是一些分布式服务框架(如RPC)中的服务地址列表。通过在ZooKeepr里创建顺序节点,能够很容易创建一个全局唯一的路径,这个路径就可以作为一个名字。

ZooKeeper  的命名服务即生成全局唯一的ID。

3、分布式协调服务/通知

    ZooKeeper  中特有 Watcher 注册与异步通知机制,能够很好的实现分布式环境下不同机器,甚至不同系统之间的通知与协调,从而实现对数据变更的实时处理。使用方法通常是不同的客户端如果 机器节点 发生了变化,那么所有订阅的客户端都能够接收到相应的Watcher通知,并做出相应的处理。

ZooKeeper 的分布式协调/通知,是一种通用的分布式系统机器间的通信方式。

4、分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式

分布式锁又分为排他锁和共享锁两种

排它锁

ZooKeeper 如何实现排它锁?

定义锁

ZooKeeper  上的一个 机器节点 可以表示一个锁

获得锁

把ZooKeeper 上的一个节点看作是一个锁,获得锁就通过创建临时节点的方式来实现。

ZooKeeper  会保证在所有客户端中,最终只有一个客户端能够创建成功,那么就可以

认为该客户端获得了锁。同时,所有没有获取到锁的客户端就需要到/exclusive_lock

节点上注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。

释放锁

因为锁是一个临时节点,释放锁有两种方式

当前获得锁的客户端机器发生宕机或重启,那么该临时节点就会被删除,释放锁

正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除,释放锁。

无论在什么情况下移除了lock节点,ZooKeeper  都会通知所有在 /exclusive_lock 节点上注册了节点变更 Watcher 监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复『获取锁』过程。

共享锁

共享锁在同一个进程中很容易实现,但是在跨进程或者在不同 Server 之间就不好实现了。ZooKeeper  却很容易实现这个功能,实现方式也是需要获得锁的 Server 创建一个 EPHEMERAL_SEQUENTIAL 目录节点,然后调用 getChildren方法获取当前的目录节点列表中最小的目录节点是不是就是自己创建的目录节点,如果正是自己创建的,那么它就获得了这个锁,如果不是那么它就调用 exists(String path, boolean watch) 方法并监控 ZooKeeper  上目录节点列表的变化,一直到自己创建的节点是列表中最小编号的目录节点,从而获得锁,释放锁很简单,只要删除前面它自己所创建的目录节点就行了。

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