ZooKeeper学习第七期--ZooKeeper一致性原理(转)

转载来源:https://www.cnblogs.com/sunddenly/p/4138580.html

1、ZooKeeper 的实现

1.1 ZooKeeper处理单点故障

咱们知道能够经过ZooKeeper对分布式系统进行Master选举,来解决分布式系统的单点故障,如图所示。html

图 1.1 ZooKeeper解决单点故障 node

那么咱们继续分析一下,ZooKeeper经过Master选举来帮助分布式系统解决单点故障,保证该系统中每时每刻只有一个Master为分布式系统提供服务。也就是说分布式的单点问题交给了ZooKeeper来处理,不知道你们此时有没有发现一个问题——"故障转移到了ZooKeeper身上"。你们看一下图就会发现,若是咱们的ZooKeeper只用一台机器来提供服务,若这台机器挂了,那么该分布式系统就直接变成双Master模式了,那么咱们在分布式系统中引入ZooKeeper也就失去了意义。那么这也就意味着,ZooKeeper在其实现的过程中要作一些可用性恢复性的保证。这样才能让咱们放心的以ZooKeeper为起点来构建咱们的分布式系统,来达到节省成本和减小bug的目的。算法

1.2 ZooKeeper运行模式

ZooKeeper服务有两种不一样的运行模式。一种是"独立模式"(standalone mode),即只有一个ZooKeeper服务器。这种模式较为简单,比较适合于测试环境,甚至能够在单元测试中采用,可是不能保证高可用性恢复性。在生产环境中的ZooKeeper一般以"复制模式"(replicated mode)运行于一个计算机集群上,这个计算机集群被称为一个"集合体"(ensemble)。数据库

图 1.2 ZooKeeper集群 服务器

ZooKeeper经过复制来实现高可用性,只要集合体中半数以上的机器处于可用状态,它就可以提供服务。例如,在一个有5个节点的集合体中,每一个Follower节点的数据都是Leader节点数据的副本,也就是说咱们的每一个节点的数据视图都是同样的,这样就能够有五个节点提供ZooKeeper服务。而且集合体中任意2台机器出现故障,均可以保证服务继续,由于剩下的3台机器超过了半数。session

注意,6个节点的集合体也只可以容忍2台机器出现故障,由于若是3台机器出现故障,剩下的3台机器没有超过集合体的半数。出于这个缘由,一个集合体一般包含奇数台机器。

从概念上来讲,ZooKeeper它所作的就是确保对Znode树的每个修改都会被复制到集合体中超过半数的机器上。若是少于半数的机器出现故障,则最少有一台机器会保存最新的状态,那么这台机器就是咱们的Leader。其他的副本最终也会更新到这个状态。若是Leader挂了,因为其余机器保存了Leader的副本,那就能够从中选出一台机器做为新的Leader继续提供服务。app

1.3 ZooKeeper的读写机制

(1) 概述分布式

ZooKeeper核心思想是,提供一个非锁机制的Wait Free用于分布式系统同步的核心服务。提供简单的文件建立、读写操做接口,其系统核心自己对文件读写并不提供加锁互斥的服务,可是提供基于版本比对的更新操做,客户端能够基于此本身实现加锁逻辑。以下图1.3所示。post

图 1.3 Using versions to prevent inconsistencies due to concurrent updates性能

 

(2) ZK集群服务

 Zookeeper是一个由多个Server组成的集群,该集群有一个Leader,多个Follower。客户端能够链接任意ZooKeeper服务节点来读写数据,以下图1.4所示。

图 1.4 ZooKeeper集群服务

ZK集群中每一个Server,都保存一份数据副本。Zookeeper使用简单的同步策略,经过如下两条基本保证来实现数据的一致性:

① 全局串行化全部的写操做

② 保证同一客户端的指令被FIFO执行(以及消息通知的FIFO

全部的读请求由Zk Server 本地响应,全部的更新请求将转发给Leader,由Leader实施。

(3) ZK组件

ZK组件,如图1.5所示。ZK组件除了请求处理器(Request Processor)之外,组成ZK服务的每个Server会复制这些组件的副本。

图 ZooKeeper组件图

 

ReplicatedDatabase是一个内存数据库,它包含了整个Data Tree。为了恢复,更新会被记录到磁盘,而且写在被应用到内存数据库以前,先被序列化到磁盘。

每个ZK Server,可服务于多个Client。Client能够链接到一台Server,来提交请求。读请求,由每台Server数据库的本地副原本进行服务。改变服务器的状态的写请求,须要经过一致性协议来处理。

做为一致性协议的一部分,来自Client的全部写请求,都要被转发到一个单独的Server,称做Leader。ZK集群中其余Server 称做Follower,负责接收Leader发来的提议消息,而且对消息转发达成一致消息层处理leader失效,同步Followers和Leader。

ZooKeeper使用自定义的原子性消息协议。因为消息传送层是原子性的,ZooKeeper可以保证本地副本不产生分歧。当leader收到一个写请求,它会计算出当写操做完成后系统将会是什么状态,接着将之转变为一个捕获状态的事务。

(4) ZK性能

ZooKeeper被应用程序普遍使用,并有数以千计的客户端同时的访问它,因此咱们须要高吞吐量。咱们为ZooKeeper 设计的工做负载的读写比例是 2:1以上。然而咱们发现,ZooKeeper的高写入吞吐量,也容许它被用于一些写占主导的工做负载。ZooKeeper经过每台Server上的本地ZK的状态副本,来提供高读取吞吐量。所以,容错性读吞吐量是以添加到该服务的服务器数量为尺度。写吞吐量并不以添加到该服务的机器数量为尺度。

例如,在它的诞生地Yahoo公司,对于写占主导的工做负载来讲,ZooKeeper的基准吞吐量已经超过每秒10000个操做;对于常规的以读为主导的工做负载来讲,吞吐量更是高出了好几倍。

2、ZooKeeper的保证

通过上面的分析,咱们知道要保证ZooKeeper服务的高可用性就须要采用分布式模式,来冗余数据写多份,写多份带来一致性问题,一致性问题又会带来性能问题,那么就此陷入了无解的死循环。那么在这,就涉及到了咱们分布式领域的著名的CAP理论,在这就简单的给你们介绍一下,关于CAP的详细内容你们能够网上查阅。

2.1 CAP理论

(1) 理论概述

分布式领域中存在CAP理论:

C:Consistency,一致性,数据一致更新,全部数据变更都是同步的。

A:Availability,可用性,系统具备好的响应性能。

P:Partition tolerance,分区容错性。以实际效果而言,分区至关于对通讯的时限要求。系统若是不能在时限内达成数据一致性,就意味着发生了分区的状况,必须就当前操做在C和A之间作出选择,也就是说不管任何消息丢失,系统均可用。

该理论已被证实:任何分布式系统只可同时知足两点,没法三者兼顾。 所以,将精力浪费在思考如何设计能知足三者的完美系统上是愚钝的,应该根据应用场景进行适当取舍。

(2) 一致性分类

一致性是指从系统外部读取系统内部的数据时,在必定约束条件下相同,即数据变更在系统内部各节点应该是同步的。根据一致性的强弱程度不一样,能够将一致性级别分为以下几种:

① 强一致性(strong consistency)。任什么时候刻,任何用户都能读取到最近一次成功更新的数据。

② 单调一致性(monotonic consistency)。任什么时候刻,任何用户一旦读到某个数据在某次更新后的值,那么就不会再读到比这个值更旧的值。也就是说,可获取的数据顺序必是单调递增的。

③ 会话一致性(session consistency)。任何用户在某次会话中,一旦读到某个数据在某次更新后的值,那么在本次会话中就不会再读到比这个值更旧的值。会话一致性是在单调一致性的基础上进一步放松约束,只保证单个用户单个会话内的单调性,在不一样用户或同一用户不一样会话间则没有保障。

最终一致性(eventual consistency)。用户只能读到某次更新后的值,但系统保证数据将最终达到彻底一致的状态,只是所需时间不能保障。

⑤ 弱一致性(weak consistency)。用户没法在肯定时间内读到最新更新的值。

2.2 ZooKeeper与CAP理论

咱们知道ZooKeeper也是一种分布式系统,它在一致性上有人认为它提供的是一种强一致性的服务(经过sync操做),也有人认为是单调一致性(更新时的大多说概念)还有人为是最终一致性(顺序一致性),反正各有各的道理这里就不在争辩了。而后它在分区容错性可用性上作了必定折中,这和CAP理论是吻合的。ZooKeeper从如下几点保证了数据的一致性

① 顺序一致性

来自任意特定客户端的更新都会按其发送顺序被提交。也就是说,若是一个客户端将Znode z的值更新为a,在以后的操做中,它又将z的值更新为b,则没有客户端可以在看到z的值是b以后再看到值a(若是没有其余对z的更新)。

② 原子性

每一个更新要么成功,要么失败。这意味着若是一个更新失败,则不会有客户端会看到这个更新的结果。

③ 单一系统映像

一个客户端不管链接到哪一台服务器,它看到的都是一样的系统视图。这意味着,若是一个客户端在同一个会话中链接到一台新的服务器,它所看到的系统状态不会比在以前服务器上所看到的更老。当一台服务器出现故障,致使它的一个客户端须要尝试链接集合体中其余的服务器时,全部滞后于故障服务器的服务器都不会接受该链接请求,除非这些服务器遇上故障服务器。

④ 持久性

一个更新一旦成功,其结果就会持久存在而且不会被撤销。这代表更新不会受到服务器故障的影响。

3、ZooKeeper原理

3.1 原理概述

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

(1) 恢复模式

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

(2) 广播模式

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

Broadcast模式极其相似于分布式事务中的2pc(two-phrase commit 两阶段提交):即Leader提起一个决议,由Followers进行投票,Leader对投票结果进行计算决定是否经过该决议,若是经过执行该决议(事务),不然什么也不作。

图3.1两阶段提交

广播模式ZooKeeper Server会接受Client请求,全部的写请求都被转发给领导者,再由领导者将更新广播给跟随者。当半数以上的跟随者已经将修改持久化以后,领导者才会提交这个更新,而后客户端才会收到一个更新成功的响应。这个用来达成共识的协议被设计成具备原子性,所以每一个修改要么成功要么失败。

图 3.2 ZooKeeper数据流动图

3.2 Zab协议详解

3.2.1 广播模式

广播模式相似一个简单的两阶段提交:Leader发起一个请求,收集选票,而且最终提交,图3.3演示了咱们协议的消息流程。咱们能够简化该两阶段提交协议,由于咱们并无"aborts"的状况。followers要么确认Leader的Propose,要么丢弃该Leader的Propose。没有"aborts"意味着,只要有指定数量的机器确认了该Propose,而不是等待全部机器的回应。

图 3.3 The flow of message with protocol

广播协议在全部的通信过程当中使用TCP的FIFO信道,经过使用该信道,使保持有序性变得很是的容易。经过FIFO信道,消息被有序的deliver。只要收到的消息一被处理,其顺序就会被保存下来。

Leader会广播已经被deliver的Proposal消息。在发出一个Proposal消息前,Leader会分配给Proposal一个单调递增的惟一id,称之为zxid。由于Zab保证了因果有序,因此递交的消息也会按照zxid进行排序。广播是把Proposal封装到消息当中,并添加到指向Follower的输出队列中,经过FIFO信道发送到 Follower。当Follower收到一个Proposal时,会将其写入到磁盘,能够的话进行批量写入。一旦被写入到磁盘媒介当中,Follower就会发送一个ACK给Leader。 当Leader收到了指定数量的ACK时,Leader将广播commit消息并在本地deliver该消息。当收到Leader发来commit消息时,Follower也会递交该消息。

须要注意的是, 该简化的两阶段提交自身并不能解决Leader故障,因此咱们 添加恢复模式来解决Leader故障。

3.2.2 恢复模式

(1) 恢复阶段概述

正常工做时Zab协议会一直处于广播模式,直到Leader故障失去了指定数量的Followers。为了保证进度,恢复过程当中必须选举出一个新Leader,而且最终让全部的Server拥有一个正确的状态。对于Leader选举,须要一个可以成功高概率的保证存活的算法。Leader选举协议,不只可以让一个Leader得知它是leader,而且有指定数量的Follower赞成该决定。若是Leader选举阶段发生错误,那么Servers将不会取得进展。最终会发生超时,从新进行Leader选举。在咱们的实现中,Leader选举有两种不一样的实现方式。若是有指定数量的Server正常运行,快速选举的完成只须要几百毫秒。

(2)恢复阶段的保证

该恢复过程的复杂部分是在一个给定的时间内,提议冲突的绝对数量。最大数量冲突提议是一个可配置的选项,可是默认是1000。为了使该协议可以即便在Leader故障的状况下也能正常运做。咱们须要作出两条具体的保证:

咱们毫不能遗忘已经被deliver的消息,若一条消息在一台机器上被deliver,那么该消息必须将在每台机器上deliver

咱们必须丢弃已经被skip的消息。

(3) 保证示例

第一条:

若一条消息在一台机器上被deliver,那么该消息必须将在每台机器上deliver,即便那台机器故障了。例如,出现了这样一种状况:Leader发送了commit消息,但在该commit消息到达其余任何机器以前,Leader发生了故障。也就是说,只有Leader本身收到了commit消息。如图3.4中的C2

图 3.4 The flow of message with protocol

图3.4是"第一条保证"(deliver消息不能忘记)的一个示例。在该图中Server1是一个Leader,咱们用L1表示,Server2和Server3为Follower。首先Leader发起了两个Proposal,P1P2,并将P一、P2发送给了Server1和Server2。而后Leader对P1发起了Commit即C1,以后又发起了一个Proposal即P3,再后来又对P2发起了commit即C2,就在此时咱们的Leader挂了。那么这时候,P3和C2这两个消息只有Leader本身收到了。

由于Leader已经deliver了该C2消息,client可以在消息中看到该事务的结果。因此该事务必须可以在其余全部的Server中deliver,最终使得client看到了一个一致性的服务视图。

第二条:

一个被skip的消息,必须仍然须要被skip。例如,发生了这样一种状况:Leader发送了propose消息,但在该propose消息到达其余任何机器以前,Leader发生了故障。也就是说,只有Leader本身收到了propose消息。如图3.4中的P3所示。

在图3.4中没有任何一个server可以看到3号提议,因此在图3.5中当server 1恢复时他须要在系统恢复时丢弃三号提议P3

图3.5

在图3.5是"第二条保证"(skip消息必须被丢弃)的一个示例。Server1挂掉之后,Server3被选举为Leader,咱们用L2表示。L2中还有未被deliver的消息P1P2,因此,L2在发出新提议P10000001P10000002以前,L2先将P1P2两个消息deliver。所以,L2先发出了两个commit消息C1C2,以后L2才发出了新的提议P10000001P10000002

若是Server1 恢复以后再次成为了Leader,此时再次将P3P10000001P10000002以后deliver,那么将违背顺序性的保障。

(4) 保证的实现

若是Leader选举协议保证了新LeaderQuorum Server中具备最高的提议编号,即Zxid最高。那么新选举出来的leader将具备全部已deliver的消息。新选举出来的Leader,在提出一个新消息以前,首先要保证事务日志中的全部消息都由Quorum FollowerProposedeliver。须要注意的是,咱们可让新Leader成为一个用最高zxid来处理事务的server,来做为一个优化。这样,做为新被选举出来的Leader,就没必要去从一组Followers中找出包含最高zxid的Followers和获取丢失的事务。

① 第一条

全部的正确启动的Servers,将会成为Leader或者跟随一个Leader。Leader可以确保它的Followers看到全部的提议,并deliver全部已经deliver的消息。经过将新链接上的Follower所没有见过的全部PROPOSAL进行排队,并以后对该Proposals的COMMIT消息进行排队,直到最后一个COMMIT消息。在全部这样的消息已经排好队以后,Leader将会把Follower加入到广播列表,以便从此的提议和确认。这一条是为了保证一致性,由于若是一条消息P已经在旧Leader-Server1中deliver,即便它刚刚将消息P deliver以后就挂了,可是当旧Leader-Server1重启恢复以后,咱们的Client就能够从该Server中看到该消息P deliver的事务,因此为了保证每个client都能看到一个一致性的视图,咱们须要将该消息在每一个Server上deliver

② 第二条

skip已经Propose,但不能deliver的消息,处理起来也比较简单。在咱们的实现中,Zxid是由64位数字组成的,低32位用做简单计数器高32位是一个epoch。每当新Leader接管它时,将获取日志中Zxid最大的epoch,新Leader Zxidepoch位设置为epoch+1counter位设置0。用epoch来标记领导关系的改变,并要求Quorum Servers 经过epoch来识别该leader,避免了多个Leader用同一个Zxid发布不一样的提议

这个方案的一个优势就是,咱们能够skip一个失败的领导者的实例,从而加速并简化了恢复过程。若是一台宕机的Server重启,并带有未发布的Proposal,那么先前的未发布的全部提议将永不会被deliver。而且它不可以成为一个新leader,由于任何一种可能的 Quorum Servers ,都会有一个Server其Proposal 来自与一个新epoch所以它具备一个较高的zxid。当Server以Follower的身份链接,领导者检查自身最后提交的提议,该提议的epoch 为Follower的最新提议的epoch(也就是图3.5中新Leader-Server2中deliver的C2提议),并告诉Follower截断事务日志直到该epoch在新Leader中deliver的最后的Proposal即C2。在图3.5中,当旧Leader-Server1链接到了新leader-Server2,leader将告诉他从事务日志中清除3号提议P3,具体点就是清除P2以后的全部提议,由于P2以后的全部提议只有旧Leader-Server1知道,其余Server不知道。

(5) Paxos与Zab

① Paxos一致性

Paxos的一致性不能达到ZooKeeper的要求,咱们能够下面一个例子。咱们假设ZK集群由三台机器组成,Server一、Server二、Server3。Server1为Leader,他生成了三条Proposal,P一、P二、P3。可是在发送完P1以后,Server1就挂了。以下图3.6所示。

图 3.6 Server1为Leader

Server1挂掉以后,Server3被选举成为Leader,由于在Server3里只有一条Proposal—P1。因此,Server3在P1的基础之上又发出了一条新Proposal—P2',P2'的Zxid为02。以下图3.7所示。

图3.7 Server2成为Leader

Server2发送完P2'以后,它也挂了。此时Server1已经重启恢复,并再次成为了Leader。那么,Server1将发送尚未被deliver的Proposal—P2和P3。因为Follower-Server2中P2'的Zxid为02和Leader-Server1中P2的Zxid相等,因此P2会被拒绝。而P3,将会被Server2接受。如图3.8所示。

图3.8 Server1再次成为Leader

咱们分析一下Follower-Server2中的Proposal,因为P2'将P2的内容覆盖了。因此致使,Server2中的Proposal-P3没法生效,由于他的父节点并不存在。

② Zab一致性

首先来分析一下,上面的示例中为何不知足ZooKeeper需求。ZooKeeper是一个树形结构,不少操做都要先检查才能肯定能不能执行,好比,在图3.8中Server2有三条Proposal。P1的事务是建立节点"/zk"P2'是建立节点"/c",而P3是建立节点 "/a/b",因为"/a"还没建,建立"a/b"就搞不定了。那么,咱们就能今后看出Paxos的一致性达不到ZooKeeper一致性的要求。

为了达到ZooKeeper所须要的一致性,ZooKeeper采用了Zab协议。Zab作了以下几条保证,来达到ZooKeeper要求的一致性。

(a) Zab要保证同一个leader的发起的事务要按顺序被apply,同时还要保证只有先前的leader的全部事务都被apply以后,新选的leader才能在发起事务。

(b) 一些已经Skip的消息,须要仍然被Skip。

我想对于第一条保证你们都能理解,它主要是为了保证每一个Server的数据视图的一致性。我重点解释一下第二条,它是如何实现。为了可以实现,Skip已经被skip的消息。咱们在Zxid中引入了epoch,以下图所示。每当Leader发生变换时,epoch位就加1,counter位置0。

图 3.9 Zxid

咱们继续使用上面的例子,看一下他是如何实现Zab的第二条保证的。咱们假设ZK集群由三台机器组成,Server一、Server二、Server3。Server1为Leader,他生成了三条Proposal,P一、P二、P3。可是在发送完P1以后,Server1就挂了。以下图3.10所示。

图 3.10 Server1为Leader

Server1挂掉以后,Server3被选举成为Leader,由于在Server3里只有一条Proposal—P1。因此,Server3在P1的基础之上又发出了一条新Proposal—P2',因为Leader发生了变换,epoch要加1,因此epoch由原来的0变成了1,而counter要置0。那么,P2'的Zxid为10以下图3.11所示。

图 3.11 Server3为Leader

Server2发送完P2'以后,它也挂了。此时Server1已经重启恢复,并再次成为了Leader。那么,Server1将发送尚未被deliver的Proposal—P2和P3。因为Server2中P2'的Zxid为10,Leader-Server1中P2P3的Zxid分别为0203P2'的epoch位高于P2P3。因此此时Leader-Server1的P2和P3都会被拒绝,那么咱们Zab的第二条保证也就实现了。如图3.12所示。

图 3.12 Server1再次成为Leader

相关文章
相关标签/搜索