您的位置:首页 > 其它

Zookeeper - 介绍篇(1)

2016-03-14 20:11 239 查看

Paxos算法

Paxos算法是Zookeeper实现的核心思路,但是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。这三个只是逻辑上的角色,真正应用中,每个实际节点可能充当多个角色。假设每个角色都是一个实际的节点,那么如下图所示:

⑴proposer 提出提案,提案信息包括提案编号和提议的value;
⑵acceptor 收到提案后可以接受(accept)提案;
⑶learner 只能”学习”被批准的提案;
算法保重一致性的基本语义:
⑴决议(value)只有在被proposers提出后才能被批准(未经批准的决议称为”提案(proposal)”);
⑵在一次Paxos算法的执行实例中,只批准(chosen)一个value;
⑶learners只能获得被批准(chosen)的value;
由上面的三个语义可演化为四个约束:
⑴P1:一个acceptor必须接受(accept)第一次收到的提案; 4000
⑵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已产生,让其尽快收敛)。

<script type="text/javascript"> $(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('<ul/>').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($('<li/>').text(i)); }; $numbering.fadeIn(1700); }); }); </script>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: