【分布式协调】ZooKeeper

本质

  高可用分布式协调者,zookeeper= 文件系统+通知机制

  可以提供的服务有:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等 

文件系统

  Zookeeper维护一个类似文件系统的数据结构

  每个子目录项如 NameService 都被称作为 znode,和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。而文件系统中只有文件节点可以存放数据而目录节点不行。

  ZooKeeper 为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得 ZooKeeper 不能用于存放大量的数据,每个节点的存放数据上限为1M

  有四种类型的znode:

  1、PERSISTENT-持久化目录节点

    客户端与zookeeper断开连接后,该节点依旧存在 

  2、 PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点

    客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号

  3、EPHEMERAL-临时目录节点

    客户端与zookeeper断开连接后,该节点被删除

  4、EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点

    客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

  存储:从逻辑上来讲,ZK 内存中的数据其实是一个树形结构,从 / 根节点开始,逐级向下用 / 分割,每一个节点下面还可以有多个子节点,就类似于 Unix 中的目录结构,但在实际中,ZK 是使用一个 HashMap 去存储整个树形结构的数据的,key 是对应的全路径字符串,value 则是一个节点对象,包含了节点的各种信息。

  ZK 在磁盘上规定了两种文件类型,一种是 log 文件,一种是 snapshot。log 文件是增量记录,负责对每一个写请求进行保存,snapshot 文件是全量记录,是对内存的快照,zk会先记录磁盘然后更新内存。

节点类型

  • leader,为客户端提供读写功能。在选举中负责投票的发起和决议。

  • follower,为客户单提供读服务,写请求转发给leader。在选举中进行投票。

  • observer,为客户端提供读服务,写请求转发leader。不参与一致性协议过半写入和选举机制,只为提高读性能。

  根据以上架构可得,服务器具有四种状态,分别是 Looking、Following、Leading、Observing

  (1) Looking:寻找 Leader 状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。

  (2)Following:跟随者状态。表明当前服务器角色是 Follower。

  (3)Leading:领导者状态。表明当前服务器角色是 Leader。

  (4)Observing:观察者状态。表明当前服务器角色是 Observer。

数据结构

  启动:每一个 ZK 节点在启动的时候,会通过读取配置文件中的集群信息,与其他节点建立 Socket 连接,集群间的通信就是通过这个 Socket。每个节点选举的时候都把自己认为的候选人信息广播出去,同时也接收来自其他节点的候选人信息,通过比较后,失败的一方会更改自己的候选人信息并重新进行广播,反复直到某一个节点得到半数以上投票,选举就完成了。

  选举:每一个节点都会维护三个最重要的信息:epoch、zxid、myid。(准确地说,epoch 和 zxid 是一个字段,一个记录在高 32 位,一个记录在低 32 位)

  • epoch 代表选举的轮次,优先比较,如果相同则继续比较下一级。
  • xid 代表本节点处理过的最大事务 ID,越大代表当前节点经手的写请求越多,知道的也就越多,第二优先级比较,如果还相同则比较 myid
  • myid 整个集群中不能重复,所以最终一定能分出胜负。胜利的节点当选 Leader。

  读写:Leader 作为集群中的老大,负责对收到的写请求发起提案PROPOSAL,告诉其他节点当前收到一个写请求,其他节点收到后,会在本地进行归档,其实就是写入文件输出流,完毕后会发送一个 ACK 给 Leader,Leader 统计到半数以上的 ACK 之后会再次发送给其他节点一个 COMMIT,其他节点收到 COMMIT 之后就可以修改内存数据了。读请求的话不需要提案直接查询内存中的数据返回即可。

  follower和observer收到读请求是一样的,直接返回本地的内存数据即可。但是写请求的话,会将当前请求转发给 Leader,然后由 Leader 去处理,就和之前的流程是一样的。

ZAP协议

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

(1) 恢复模式

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

(2)广播模式

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

CAP应用

  无例外,zookeeper也遵循cap原则,保证cp追求a,一致性是zookeeper最必要的,p是必要的,所以只能追求a了,zab算法的过程如下

  (1)ZooKeeper写数据都是leader节点,leader节点会把数据通过proposal请求发送到所有节点

  (2)所有节点收到数据以后都会写到自己到本地磁盘上面,成功后发送一个ack请求给leader

  (3)leader只要接受到过半的节点ack响应,就会发送commit消息给各个节点,各个节点就会把消息放入到内存中

  (4)返回客户端写入成功 

Paxos 和 ZAP

  相同点:

  (1)两者都存在一个类似于 Leader 进程的角色,由其负责协调多个 Follower 进程的运行

  (2)Leader 进程都会等待超过半数的 Follower 做出正确的反馈后,才会将一个提案进行提交

  (3)ZAB 协议中,每个 Proposal 中都包含一个 epoch 值来代表当前的 Leader周期,Paxos 中名字为 Ballot

  不同点:

  ZAB 用来构建高可用的分布式数据主备系统(Zookeeper),Paxos 是用来构建分布式一致性状态机系统。

  一致性算法算法解决的什么问题呢,解决的就是保证每个节点执行相同的操作序列。因为在zookeeper中就是保证各个操作的事务一致性,只有所有的事务按照一定的顺序执行,那么得到的结构必然是事务一致性的。

  在这里一致性算法通过投票来对写操作进行全局编号,同一时刻,只有一个写操作被批准,同时并发的写操作要去争取选票,只有获得过半数选票的写操作才会 被批准(所以永远只会有一个写操作得到批准),其他的写操作竞争失败只好再发起一轮投票,就这样,所有写操作都被严格编号排序。任何一个节点挂掉都不会影响整个集群的数据一致性(总2n+1台,除非挂掉大于n台)。

Watcher机制

  ZooKeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher 通知状态和事件类型做出业务上的改变,这样一种机制可以让Zookeeper实现很多的场景,比如配置中心、注册中心等

  数据结构:ZooKeeper 使用 WatchedEvent 对象来封装服务端事件并传递。 该对象包含了每个事件的 3 个基本属性:

  • 通知状态( keeperState )
  • 事件类型( EventType )
  • 节点路径( path )。

  机制:Watch机制采用了Push的方式来实现,也就是说客户端和Zookeeper Server会建立一个长连接,一旦监听的指定节点发生了变化,就会通过这个长连接把变化的事件推送给客户端。

(1)客户端注册 watcher

(2)服务端处理 watcher

(3)客户端回调 watcher

  • 首先,是客户端通过指定命令比如existsgetData、getChildren,对特定路径增加watch,这些命令是一次性的watch,而addWatch方法是永久性的Watch
  • 然后服务端收到请求以后,用HashMap保存这个客户端会话以及对应关注的节点路径,同时客户端也会使用HashMap
    存储指定节点和事件回调函数的对应关系。
  • 当服务端指定被watch的节点发生变化后,就会找到这个节点对应的会话,把变化的事件和节点信息发给这个客户端。
  • 客户端收到请求以后,从ZkWatcherManager里面对应的回调方法进行调用,完成事件变更的通知。

  特征

  • 一次性:无论是服务端还是客户端,一旦一个 Watcher 被触发 ,ZooKeeper 都会将其从相应的存储中移除。这样的设计有效的减轻了服务端的压力,不然对于更新非常频繁的节点,服务端会不断的向客户端发送事件通知,无论对于网络还是服务端的压力都非常大。
  • 客户端串行执行:客户端 Watcher 回调的过程是一个串行同步的过程。也就是注册多个但一个个执行的意思。
  • 轻量:
    • Watcher 通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。
    • 客户端向服务端注册 Watcher 的时候,并不会把客户端真实的 Watcher 对象实体传递到服务端,仅仅是在客户端请求中使用 Boolean 类型属性进行了标记。
  • Watcher Event 异步发送:Watcher的通知事件从 Server 发送到 Client 是异步的,这就存在一个问题,不同的客户端和服务器之间通过 Socket 进行通信,由于网络延迟或其他因素导致客户端在不同的时刻监听到事件,由于 ZooKeeper 本身提供了 Ordering Guarantee(消息有顺序串行的发送),即客户端监听事件后,才会感知它所监视 Znode发生了变化。所以我们使用 ZooKeeper 不能期望能够监控到节点即时的变化。ZooKeeper 只能保证最终的一致性,而无法保证强同步一致性。 
  • 客户端注册 Watcher时机:GetData、Exists、GetChildren。
  • 服务器触发 Watcher时机:Create、Delete、SetData。
  • 当一个客户端连接到一个新的服务器上时,Watch 将会被以任意会话事件触发。
    • 当与一个服务器失去连接的时候,是无法接收到 Watch 的。而当 Client 重新连接时,如果需要的话,所有先前注册过的 Watch,都会被重新注册。通常这是完全透明的。
    • 只有在一个特殊情况下,Watch 可能会丢失:对于一个未创建的 Znode的 Exist Watch,如果在客户端断开连接期间被创建了,并且随后在客户端连接上之前又删除了,这种情况下,这个 Watch 事件可能会被丢失。

  例子

  官方声明:一个 Watch 事件是一个一次性的触发器,当被设置了 Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了 Watch 的客户端,以便通知它们。

  为什么不是永久的,举个例子,如果服务端变动频繁,而监听的客户端很多情况下,每次变动都要通知到所有的客户端,给网络和服务器造成很大压力。

  一般是客户端执行 getData(“/节点 A”,true),如果节点 A 发生了变更或删除,客户端会得到它的 watch 事件,但是在之后节点 A 又发生了变更,而客户端又没有设置 watch 事件,就不再给客户端发送。

  在实际应用中,很多情况下,我们的客户端不需要知道服务端的每一次变动,我只要最新的数据即可。 

应用场景:

  ZooKeeper 是一个典型的发布/订阅模式的分布式数据管理与协调框架,开发人员可以使用它来进行分布式数据的发布和订阅。

  配置中心:

  即所谓的配置中心,顾名思义就是发布者发布数据供订阅者进行数据订阅。目的动态获取数据(配置信息)实现数据(配置信息)的集中式管理和数据的动态更新

  (1)数据量通常比较小

  (2)数据内容在运行时会发生动态更新

  (3)集群中各机器共享,配置一致

  如:机器列表信息、运行时开关配置、数据库配置信息等

  服务发现:

  zk 的命名服务命名服务是指通过指定的名字来获取资源或者服务的地址,利用 zk 创建一个全局的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。

  分布式通知和协调中介者:

  对于系统调度来说:操作人员发送通知实际是通过控制台改变某个节点的状态,然后 zk 将这些变化发送给注册了这个节点的 watcher 的所有客户端。

  对于执行情况汇报:每个工作进程都在某个目录下创建一个临时节点。并携带工作的进度数据,这样汇总的进程可以监控目录子节点的变化获得工作进度的实时的全局情况。

  分布式锁(文件系统、通知机制)

  有了 ZooKeeper 的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序的公平锁。

  • 对于第一类,我们将zookeeper上的一个znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。厕所有言:来也冲冲,去也冲冲,用完删除掉自己创建的distribute_lock 节点就释放出锁。
  • 对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。

  集群管理:

  所谓集群管理无在乎两点:是否有机器退出和加入、选举master。

  • 第一点,所有机器约定在父目录GroupMembers下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除。新机器加入 也是类似,所有机器收到通知:新兄弟目录加入。
  • 第二点,我们稍微改变一下,所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。

  队列管理:两种类型的队列:

  1、 同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。

  2、队列按照 FIFO 方式进行入队和出队操作。

  第一类,在约定目录下创建临时目录节点,监听节点数目是否是我们要求的数目。

  第二类,和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号

posted @ 2020-10-12 14:35  饭小胖  阅读(159)  评论(0编辑  收藏  举报