您的位置:首页 > 其它

Zookeeper - 介绍篇(1)

2016-03-14 20:11 204 查看

Paxos算法

Paxos算法是Zookeeper实现的核心思路ZAB协议的基础,但是Paxos算法理解比较抽象。这里先简述下我的理解:

首先,Paxos算法解决的问题是:每一个处于正常工作的服务端都执行一个相同的命令序列。

意思就是:在如下的分布式系统中,有多个Client和多个Server。



每个client都可以和每个server进行交互。假设每个Client此时各发一个请求,分别是O1,O2,O3,O4。那么在每个服务器上看到的命令顺序可能是不一样的。Paxos算法就是要保证每个服务端接受的命令顺序是完全一样的。Paxos考虑的问题是,在异步通信过程中,发送的数据可能会被丢失(lost)、延长(delayed)、重复(duplicated),但不会出现被篡改。同时任意一个服务端不会出现拜占庭将军问题(Byzantine failure),可以简单地理解为结点群在决定命令序列的过程中没有结点受病毒、黑客影响。

一个简单的解决办法就是,利用一个中间代理,所有的客户端的请求都转发到这个节点上,然后再由这个节点转发到每个服务器上。但是,首先这样涉及到分布式事务问题。因为这个中间代理需要保证每个Server上的数据是一致的。根据CAP理论,这样在实时追求一致性的同时必然效率比较低。然后,做这样一个转发节点也要考虑很多问题,比如这个节点挂掉怎么办等等。

Paxos算法可以很好地解决这个问题,Paxos包括三种角色:Proposer,Acceptor和Learner。这三个只是逻辑上的角色,真正应用中,每个实际节点可能充当多个角色。假设每个角色都是一个实际的节点,那么如下图所示:

我们这里假设这是一个银行账户系统,C1,C2,C3还有C4共用同一个银行账户。账户数据被冗余存储(每个服务器上保存相同的数据)在S1~S5上。假设如下:

⑴proposer 提出提案,提案信息包括提案编号和提议的value;

⑵acceptor 收到提案后可以接受(accept)提案;

⑶learner 只能”学习”被批准的提案;

算法保重一致性的基本语义:

我们通过举例来说明,首先看一个最简单的情况,就是只有一个Acceptor的情况,用来说明我们的问题以及要求:



首先,每个客户端C1~C4能看到当前存款,然后对于当前存款进行操作,可以取出或者存款,客户端向proposer发送的是最终的存款值,然后等待成功或者失败。

首先我们有一个明确的需求就是,每次只能有一个客户端的请求是成功的。也就是说在同一个提案(看到余额为同一个值的时候)。但是区分同一个提案只通过值还不行,我们还要一个额外的版本号,来区分是哪一次看到的余额为800(比如上图中,通过几次重试,假设C2,C3,C4的分别先成功,C1看到的还是800,但是并不是最开始的800)。

之后,只要请求成功,那么每个服务器上的存款的是这个请求的值。

但是,只有一个Acceptor是不够的,因为只要这个Acceptor挂掉,系统就无法继续运行。我们这里搞3个Acceptor。

用术语可以总结为:

⑴决议(value)只有在被proposers提出后才能被批准(未经批准的决议称为”提案(proposal)”);

⑵在一次Paxos算法的执行实例中,只批准(chosen)一个value;

⑶learners只能获得被批准(chosen)的value;

由上面的三个语义可演化为四个约束:

⑴P1:一个acceptor必须接受(accept)第一次收到的提案;

⑵P2a:一旦一个具有value v的提案被批准(chosen),那么之后任何acceptor 再次接受(accept)的提案必须具有value v;

⑶P2b:一旦一个具有value v的提案被批准(chosen),那么以后任何 proposer 提出的提案必须具有value v;

⑷P2c:如果一个编号为n的提案具有value v,那么存在一个多数派,要么他们中所有人都没有接受(accept)编号小于n的任何提案,要么他们已经接受(accpet)的所有编号小于n的提案中编号最大的那个提案具有value v;

每个Client会把各自的请求发给proposer,由proposer解析并发出提案(假设把请求翻译成value v)。



之后proposer向每个Acceptor发送prepare,prepare中包含一个sequence number(SEQ)。如下图所示,蓝色箭头表示先到达,绿色箭头表示后到达。A1,A2先接收到P1的prepare,A3先接收到P2的prepare;



当Acceptor接收到prepare请求SEQn时,检查自身上次回复过的prepare请求SEQp

a). 如果SEQp>SEQn,则忽略此请求,直接结束本次批准过程;

b). 否则检查上次批准的accept请求[SEQx,Vx],并且回复[SEQx,Vx];如果之前没有进行过批准,则简单回复ok;



如上图所示,绿色箭头对应之前发送prepare的绿色箭头的响应,蓝色箭头同理。对于A1,接收到SEQ=1,由于自己为空,将自己的SEQ设为1,并回复OK。之后接受到P2的SEQ=0,由于小于1,所以回复reject,并包含SEQ=1;对于A2,同理。对于A3,接收到SEQ=0,由于自己为空,将自己的SEQ设为0,并回复OK。之后接收到SEQ=1,由于大于自己的0,将自己的SEQ设为1,并回复OK。

由于P1收到了半数以上的OK,则向所有Acceptors发送accept[SEQ=1,Value=V1]信息(假设P1先发送V1之后发送V2)。而P2收到了reject(只要收到了reject,就证明集群中有更新的节点),则将SEQ设为接收到的SEQ(1)+1就是2,继续发送prepare。

假设先处理P1的accept,并且每个Acceptors都收到了。

接收accept[SEQ=1,Value=V1],如果接收到的SEQ小于自己的SEQx,那么回复Nack(SEQx)信息(暗示了该Proposer提完案后至少有一个其余的Proposer广播了具有更高编号的提案);否则设置自己的Value为V1,并且回复Accepted信息。



P1接收到了多数的Accepted的信息,表明提案被接受。Learner开始学习,将value写入server。



之后假设P1和P2同时发送prepare以发送进一步的提案。



继续工作,如果一切正常,和之前的同理,假设P1的prepare请求先到了每个Acceptor,这次因为每个Acceptors有值了,所以回复Promise[SEQ=1,value=V1]. 这时,假设P2的prepare请求到达了每个Acceptor,那么更新每个Acceptor的SEQ为2,之后每个Acceptor回复Promise[SEQ=2,value=V1]。



因为P1接收到了多数的Promise,所以会发送accept[SEQ=1,value=V2]。但是,每个Acceptor的SEQ已经为2,所以,每个Acceptor都会回复Nack(SEQ=2)。P1检测到Nack包,会把SEQ=2+1=3作为新的SEQ继续尝试prepare。这次应该为P2的提案通过。

假设这时A3挂掉,只有A1,A2更新了Value,之后提案通过触发learner更新Server的Value。



之后同样的流程,假设P1提案成功,value更新为V2.



这时A3恢复,P2提案发出。按照之前的规则,由于A3的SEQ=1,这时P2发出SEQ=4,还是可以成功更新。

正是按照这种规则,保证了在S1,S2,S3,S4,S5上的命令顺序一致性。并且实现了2F+1的容错能力,即具备2F+1个结点的系统最多允许F个结点同时出现故障。

快速Paxos算法

Paxos算法可能出现死循环,就是在两个Proposer总是在交替prepare。并且,Paxos算法在出现竞争的情况下,其收敛速度很慢,甚至可能出现活锁的情况,例如当有三个及三个以上的proposer在发送prepare请求后,很难有一个proposer收到半数以上的回复而不断地执行prepare。因此,为了避免竞争,加快收敛的速度,在算法中引入了一个Leader这个角色,在正常情况下同时应该最多只能有一个参与者扮演Leader角色,而其它的参与者则扮演Acceptor的角色,同时所有的人又都扮演Learner的角色。

在这种优化算法中,只有Leader可以提出议案,从而避免了竞争使得算法能够快速地收敛而趋于一致,此时的paxos算法在本质上就退变为两阶段提交协议。但在异常情况下,系统可能会出现多Leader的情况,但这并不会破坏算法对一致性的保证,此时多个Leader都可以提出自己的提案,优化的算法就退化成了原始的paxos算法。

一个Leader的工作流程主要有分为三个阶段:

(1)学习阶段 向其它的参与者学习自己不知道的数据(决议);当一个参与者成为了Leader之后,它应该需要知道绝大多数的paxos实例,因此就会马上启动一个主动学习的过程。假设当前的新Leader早就知道了1-134、138和139的paxos实例,那么它会执行135-137和大于139的paxos实例的第一阶段。如果只检测到135和140的paxos实例有确定的值,那它最后就会知道1-135以及138-140的paxos实例。

(2)同步阶段 让绝大多数参与者保持数据(决议)的一致性;此时的Leader已经知道了1-135、138-140的paxos实例,那么它就会重新执行1-135的paxos实例,以保证绝大多数参与者在1-135的paxos实例上是保持一致的。至于139-140的paxos实例,它并不马上执行138-140的paxos实例,而是等到在服务阶段填充了136、137的paxos实例之后再执行。这里之所以要填充间隔,是为了避免以后的Leader总是要学习这些间隔中的paxos实例,而这些paxos实例又没有对应的确定值。

(3)服务阶段 为客户端服务,提案;Leader将用户的请求转化为对应的paxos实例,当然,它可以并发的执行多个paxos实例,当这个Leader出现异常之后,就很有可能造成paxos实例出现间断。

问题

Leader的选举原则?

Acceptor如何感知当前Leader的失败,客户如何知道当前的Leader?

当出现多Leader之后,如何kill掉多余的Leader?

如何动态的扩展Acceptor?

Zookeeper的核心实现(以下为转载)

在Zookeeper集群中,主要分为三者角色,而每一个节点同时只能扮演一种角色,这三种角色分别是:

(1)Leader:接受所有Follower的提案请求并统一协调发起提案的投票,负责与所有的Follower进行内部的数据交换(同步);

(2)Follower: 直接为客户端服务并参与提案的投票,同时与Leader进行数据交换(同步);

(3)Observer: 直接为客户端服务但并不参与提案的投票,同时也与Leader进行数据交换(同步);



每个Zookeeper节点就是一个QuorumPeer:



Zookeeper对于每个节点QuorumPeer的设计相当的灵活,QuorumPeer主要包括四个组件:客户端请求接收器(ServerCnxnFactory)、数据引擎(ZKDatabase)、选举器(Election)、核心功能组件(Leader/Follower/Observer)。其中:

(1)ServerCnxnFactory负责维护与客户端的连接(接收客户端的请求并发送相应的响应);

(2)ZKDatabase负责存储/加载/查找数据(基于目录树结构的KV+操作日志+客户端Session);

(3)Election负责选举集群的一个Leader节点;

(4)Leader/Follower/Observer一个QuorumPeer节点应该完成的核心职责;

QuorumPeer工作流程

1.Leader职责



Follower确认: 等待所有的Follower连接注册,若在规定的时间内收到合法的Follower注册数量,则确认成功;否则,确认失败。

2.Follower职责



选举算法

LeaderElection选举算法



选举线程由当前Server发起选举的线程担任,他主要的功能对投票结果进行统计,并选出推荐的Server。选举线程首先向所有Server发起一次询问(包括自己),被询问方,根据自己当前的状态作相应的回复,选举线程收到回复后,验证是否是自己发起的询问(验证xid 是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议 的

leader 相关信息(id,zxid),并将这些 信息存储到当次选举的投票记录表中,当向所有Server

都询问完以后,对统计结果进行筛选并进行统计,计算出当次询问后获胜的是哪一个Server,并将当前zxid最大的Server 设置为当前Server要推荐的Server(有可能是自己,也有可以是其它的Server,根据投票结果而定,但是每一个Server在第一次投票时都会投自己),如果此时获胜的Server获得n/2 + 1的Server票数,设置当前推荐的leader为获胜的Server。根据获胜的Server相关信息设置自己的状态。每一个Server都重复以上流程直到选举出Leader。

初始化选票(第一张选票): 每个quorum节点一开始都投给自己;

收集选票: 使用UDP协议尽量收集所有quorum节点当前的选票(单线程/同步方式),超时设置200ms;

统计选票:

1).每个quorum节点的票数;

2).为自己产生一张新选票(zxid、myid均最大);

选举成功: 某一个quorum节点的票数超过半数;

更新选票: 在本轮选举失败的情况下,当前quorum节点会从收集的选票中选取合适的选票(zxid、myid均最大)作为自己下一轮选举的投票;

异常问题的处理

1). 选举过程中,Server的加入

当一个Server启动时它都会发起一次选举,此时由选举线程发起相关流程,那么每个 Server都会获得当前zxid最大的那个Server是谁,如果当次最大的Server没有获得n/2+1 个票数,那么下一次投票时,他将向zxid最大的Server投票,重复以上流程,最后一定能选举出一个Leader。

2). 选举过程中,Server的退出

只要保证n/2+1个Server存活就没有任何问题,如果少于n/2+1个Server 存活就没办法选出Leader。

3). 选举过程中,Leader死亡

当选举出Leader以后,此时每个Server应该是什么状态(FLLOWING)都已经确定,此时由于Leader已经死亡我们就不管它,其它的Fllower按正常的流程继续下去,当完成这个流程以后,所有的Fllower都会向Leader发送Ping消息,如果无法ping通,就改变自己的状为(FLLOWING ==> LOOKING),发起新的一轮选举。

4). 选举完成以后,Leader死亡

处理过程同上。

5). 双主问题

Leader的选举是保证只产生一个公认的Leader的,而且Follower重新选举与旧Leader恢复并退出基本上是同时发生的,当Follower无法ping同Leader是就认为Leader已经出问题开始重新选举,Leader收到Follower的ping没有达到半数以上则要退出Leader重新选举。

FastLeaderElection选举算法

FastLeaderElection是标准的fast paxos的实现,它首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决 epoch 和 zxid 的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息。

FastLeaderElection算法通过异步的通信方式来收集其它节点的选票,同时在分析选票时又根据投票者的当前状态来作不同的处理,以加快Leader的选举进程。

每个Server都一个接收线程池和一个发送线程池, 在没有发起选举时,这两个线程池处于阻塞状态,直到有消息到来时才解除阻塞并处理消息,同时每个Serve r都有一个选举线程(可以发起选举的线程担任)。

1). 主动发起选举端(选举线程)的处理

首先自己的 logicalclock加1,然后生成notification消息,并将消息放入发送队列中, 系统中配置有几个Server就生成几条消息,保证每个Server都能收到此消息,如果当前Server 的状态是LOOKING就一直循环检查接收队列是否有消息,如果有消息,根据消息中对方的状态进行相应的处理。

2).主动发送消息端(发送线程池)的处理

将要发送的消息由Notification消息转换成ToSend消息,然后发送对方,并等待对方的回复。

3). 被动接收消息端(接收线程池)的处理

将收到的消息转换成Notification消息放入接收队列中,如果对方Server的epoch小于logicalclock则向其发送一个消息(让其更新epoch);如果对方Server处于Looking状态,自己则处于Following或Leading状态,则也发送一个消息(当前Leader已产生,让其尽快收敛)。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  zookeeper 分布式 paxos