In Search of an Understandable Consensus Algorithm

1. Raft是什么     

        这篇文章主要介绍分布式协调一致性算法Raft。  一致性问题是分布式系统中重要的问题,它涉及到多个server对特定值达成共识。解决这类问题的算法业界比较流行的算法是Paxos。 一致性算法一般要具有以下特点:

1.  它是安全的,不会返回错误的结果,即使在网络延时、丢包、重复等多种情况下。

2. 保证系统是可用的,即使有些节点已经宕机。

3. 不依赖于时间去确保日志的一致性。 

4. 一个命令的执行只需要在多数系统中执行完,就算真正完成。小部分节点未完成不影响其执行。

        为了达到上面的特性,Raft算法是采用状态机复制模型(State Machine Replication),状态机复制的思想是,将每个副本节点看作一个状态机,而节点上的数据看作其状态,而提交的对数据的修改操作是输入,给定状态和输入,则状态机所迁移到的状态和产生的输出是确定的。那么如果所有的节点的初始状态一致,而且都执行相同的命令序列,那么最终也会达到相同的状态。 和Paxos算法一样,Raft算法主要的目的也是为了保证状态机复制在各个副本上的顺序是一致的。

        Raft算法是由斯坦福大学Diego Ongaro 和John Ousterhout提出的,尽管它跟Paxos功能和性能上差别不大,但它比Paxos更易懂。这也是作者设计这个算法的目的。Diego认为Paxos主要的不足在于让人难以理解,并且其架构不适合与构建真实系统。所以Raft的设计原则就是让人更可理解,在后续的很多选择中都可以体会到这点。与Paxos类似, 也是采用中心化的方式,所有的写操作都只能通过Leader完成,其他节点只能跟随。 它只定义两种远程过程调用(PRC)以及其响应,就完成了一致性算法的要求,更加容易实现于现实系统中。  

2.Raft把Server分为三种状态:

1.) Leader

     整个集群只有唯一的Leader,所有的写操作都只能通过Leader,再由Leader把修改数据发给Follower。

2.) Follower

    追随者,接受Leader发过来的日志。

3.) Candidate

    候选者,Follower在超时时间内还没接收到Leader的心跳,则自动变为候选者。

         

        上图是它的状态转换图, 一开始所有server状态都为Follower,当在超时时间未收到Leader的心跳或命令,则变为Candidate; Candidate通过

选举得到了多数的票数后,变为Leader。Leader会一直做下去,直到其宕机重启或发现有比他大的Term(后面讲)


3. Raft的时间段

        Raft把时间分为一个一个的Term, 在一个Term里最多只能有一个Leader。用Term来表示可以使得集群不依赖与时间,只需要用Term就能判断当前Leader的作用期限。

当一个Leader宕机后,其他follower在timeout时间内收不到心跳,则自动把Term自增1并变为Candidate发起投票。 所有每个Term都是以选举阶段开始的,当Leader产生后,就进入日志复制阶段,Term维持不变直到Leader宕机或重启。

         

     

4. Raft的通讯

      Raft Server采用RPC(remote procedure call)进行通讯,只有2种RPC以及其回应

1.) Request Vote RPCs

      这个RPC是Candidate在发起投票请求时向其他server发送的, 其他server会根据日志以及Term信息给RPC返回一个响应,具体看Leader选举。

      

2.) AppendEntries RPCs

      这个RPC是Leader向Follower发送心跳或者复制日志时用的, Follower也需要给Leader回应。

      

5. Leader选举

      集群内的Raft Server一启动是以Follower的状态存在,每个Follower都会设置一个随机的选举超时时间,当超时时间内未接受到Leader的心跳或日志,Follower会改变状态变为Candidate而发起投票。 因为此时无Leader存在,所以必定有第一个Follower因为超时而变为Candidate状态而发起选举并投自己一票。 其他server收到Request Vote请求后,根据当前状态进行判断是否要投赞成票:

     1.) 如果Candidate的Term比其本身的Term少,则投反对票。 如果发现Candidate的Term更大,则设置本身的Term为接收到的新Term,然后看条件2

     2.)   如果未投票并且Candidate的日志比其要更新,则投赞成票。

     当一个Candidate收到超过半数的投票,则认为其当选成为Leader。每轮投票至多只有一个Candidate当选,因为每人只能投一票而且要超过半数才能当选。但也有可能一轮投票无人当选。此时只能等待下一次选举超时重新起一轮投票。如果同时很多Follower因选举超时而变为Candidate,会导致当前Term无法产生Leader,又要等下一轮。这种情况称为Spilt Vote。 

    为了避免Spilt Vote,Raft对每个Server采用随机的选举超时时间,一般是150~300ms之间。这样可以减少同一时间内出现很多Candidate的情况。 一般最新超时的Follower能很快就当选,然后发送心跳给其他server,防止他们超时。


6.日志复制

       只有Leader才能通过AppendEntries RPC向其他Server发送日志,要求其根据日志执行命令,此过程称为日志复制。我们先来看看日志长什么样。

      

        如上图,一个框框就是一个日志项,里面有Term信息和命令。同时日志项上还有一个index,标记其在日志的位置。 当一个日志在多数server都保存后,就可以认为日志是提交了,当然后续还有一些针对提交日志的限制,这个后面讲。

        日志有如下性质:

       1.) 如果两个日志的Term和Index相等,则其命令也是相等。

       2.) 如果两个日志的Term和Index相等, 则其之前的日志也是相等的。

       第一个性质因为每个Term只有一个Leader, 并且Leader的日志从来不会被覆盖,只会不断增加,所以显然是满足的。 第二个性质是因为Follower在接受到AppendEntries RPC时, 会检查RPC中新日志项的前一个日志项是否包含在本身中,否则会拒绝增加新日志项。

       当Follower因为检测前一个项不存在而拒绝RPC时, Leader收到回应后会尝试发更前一个日志项,一直到Follower接受为止。这种情况特别适合于Leader重启,因为Leader重启后不知道Follower到底保存了哪些日志,它会先尝试从最后的日志开始复制,直到找到Follower真正能接受的日志项,然后才恢复正常的日志复制。

 

7.  安全性

      为了保证所有的Server能够在任何条件下都能执行相同的命令,还需要加一些约束条件。

1.) 选举约束

       选举必须保证本次选出来的Leader必须是含有上一个Term中已经commit的日志。Raft为了满足这个条件,规定日志只可以从Leader发往Follower,Leader日志只能增加不能被覆盖。同时在投票时,候选者必须得到多数的票数,而每一个已提交的日志必定包含在这些多数的server中,那么得到多数的投票,必定这个候选者是在多数server中日志是最新的,所有其必定含有所有已经提交的日志。  

        注意当server少于原来的一半时,此时不能选出Leader,即集群不能工作,所以不会存在已commit的日志在现有server中不存在的情况下还能选出Leader的情况。


2.) 不能提交前非当前Term的日志

       Leader标记日志为提交时,只能标记那些已经复制到多数Follower,并且Term是当前Term的日志,不能标记以前Term的日志为 commit。这样的约束是为了避免以下这种情况的:

       

            如上图描述, (a)图S1已经复制index 2到S2后, S1 宕机。  (b) S5 在这种情况下,可能比S2先超时,导致先变为Candidate,它可以得到S3,S4的票数和自己的票,所以能成为Leader, 并开始一个新的Term,同时index 2有一个新命令(注意S3,S4的Term都变为3了,只不过未有新日志,所以未标) (c) S5刚当选就宕机, 此时S1重启,并当选为Leader。 开了一个新Term,但继续复制Log 2 变成多数,但未提交。  (d) 此时S1又挂了, S5有可能再当选(注意S5重启后保持原来的新Term, 此时比其他server的Term大小是小的,但仍然有可能当选,只要每次选举是超时时间都最短,总是他先发起选举即可), 这样就会继续复制之前的3号日志,从而把其他server的2号日志覆盖。  (e)只有像S1把当前term 4复制到多数,此时才算提交,然后以前的日志就被默认提交。


8. Leader 完备性

           定义:  如果一个日志在之前的Term被提交,则其日志会被包含在后续的更高Term的Leader日志中。

           要证明完备性,关键点在于多数server中投赞成票的voter中。 假设term T已经提交了一个日志, term U是最小的其LeaderU不包括commit日志, U > T。那么在选举LeaderU的时候,必定有个多数派中的voter含有term T提交的日志,但又投票给LeaderU。 但是投票的原则是LeaderU要比voter的日志更新,这里的更新是指LeaderU的日志的Term比voter大或者Term一样,但最后日志的index较大。  如果是LeaderU的Term较大,则LeaderU的前一个给它复制日志的Leader必定包含该commit日志(根据假设),同时它的term比voter大,根据日志复制的性质,也要包含该commit日志,得到矛盾(试想一下要LeaderU丢失日志,只有其前一个Term的Leader丢失并在复制的时候把LeaderU的日志回滚才会产生); 如果是LeaderU的Term一样大,但index较大,根据复制日志的性质,LeaderU也必定包含该commit日志,也得到矛盾。 所以得证。


   

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值