分布式系统中主要的问题就是如何保持节点状态的一致性,不论发生任何failure,只要集群中大部分的节点可以正常工作,则这些节点具有相同的状态,保持一致,在client看来相当于一台机器。

  一致性问题本质就是replicated state machines,即所有结点都从同一个state出发,都经过同样的一些操作序列(log),最后到达同样的state。其中保证各个节点执行相同的操作序列就是raft算法所要实现的。在raft算法中有一个Leader的角色,client与之进行交互,并且Leader协调Follower,保障所有的Follower具有相同的操作序列,最后提交这些操作,使状态机达成一个新的一致的stat。

  整个raft算法分为Leader选举,日志分发,日志压缩(快照),集群成员变更。其中的Leader选举是算法的核心部分。算法保证任何时候最多只有一个Leader,但是可能没有Leader(比如正在选举过程中或者集群成员多数不可用时)。在Leader确立之后,就可以进行日志分发,算法保证日志会被安全的分发到集群中并且应用到状态机的日志和自己相同。快照是为了减少日志量,去除中间过程。集群成员变更是为了在不停服务的情况下安全使用新的集群配置。

  Raft在非拜占庭错误情况下,包括网络延迟、分区、丢包、冗余和乱序等错误都可以保证正确,不会返回错误结果,这就是安全性保证。实际上就是保证所有成员状态机都以同样的顺序,执行同样的命令。下面分析几种典型的场景下,raft是如何保证安全性的。

1. Leader选举之后,如果Follower与Leader日志有冲突该如何处理?

  Raft规定Follower中的冲突日志会被Leader中的日志覆盖,使得Follower中的日志总是与Leader的日志保持一致。Leader必须找到Follower日志中最后两者达成一致的地方,然后删除从那个点之后的所有日志条目,发送自己的日志给Follower。所有的这些操作都在进行日志复制RPC的一致性检查时完成: Leader针对每一个Follower维护了一个 nextIndex,这表示下一个需要发送给Follower的日志条目的index。当一个Leader刚获得权力的时候,他初始化所有的 nextIndex 值为自己的最后一条日志的index加1。Leader每次发送日志复制RPC的时候会包含两个值:prevLogIndex和prevLogTerm分别对应紧随新日志条目之前日志的索引值(index)和任期号(term),即prevLogIndex = newIndex - 1,如果Follower在它的日志中找不到包含Leader发送过来的index和term的条目,那么他就会拒绝接收新的日志条目。也就是此时Follower的日志和Leader不一致,日志复制RPC 时的一致性检查就会失败。在被Follower拒绝之后,Leader就会减小 nextIndex 值并进行重试。最终 nextIndex 会在某个位置使得Leader和Follower的日志达成一致。当这种情况发生,日志复制 RPC 就会成功,这时就会把Follower冲突的日志条目全部删除并且加上Leader的日志。一旦日志复制 RPC 成功,那么Follower的日志就会和Leader保持一致,并且在接下来的任期里一直继续保持。通过上述步骤,Raft算法保证了日志是顺序复制的。就是说,假如有一条旧的日志还未复制给FollowerA,那么更新的日志就不能复制。日志的顺序复制,很大程度上简化了Raft算法。比如查看各成员日志的新旧,只要比较最后一条日志即可。

 2. 如果在一个Follower宕机的时候Leader提交了若干的日志条目,然后这个Follower上线后可能会被选举为Leader并且覆盖这些日志条目,如此就会产生不一致。

Raft通过对Leader的选举进行限制,来保证所新选出的任何Leader对于给定的任期号,都拥有了之前任期的所有被提交的日志条目,限制规则为:candidate发送出去的投票请求消息必须带上其最后一条日志条目的Index与Term;接收者需要判断该Index与Term至少与本地日志的最后一条日志条目一样新,否则不给投票。因为 前一个Leader提交日志条目的条件是日志复制给集群中的多数成员,Candidate选举为Leader的条件也是需要多数成员的投票。那么这两个大多数中成员必定有一个交叉,即有一个成员具有该日志,并且投票给了新Leader,也就意味着新Leader的日志至少不比该成员旧,那么新Leader也具有该日志。这样就得到证明了,后续的Leader一定具有前面Leader提交的日志。

3. 即使保证上述选举规则,也不能保证一致性,也就是说会出现Leader提交了前面任期的日志条目之后,该条目还有可能被后来的Leader覆盖而产生不一致。如下图所示:

  • (a) S1是Leader,并且部分地复制了index-2;
  • (b)  S1宕机,S5得到S3、S4、S5的投票当选为新的Leader(S2不会选择S5,因为S2的日志较S5新),并且在index-2写入到一个新的条目,此时是term=3(注:之所以是term=3,是因为在term-2的选举中,S3、S4、S5至少有一个参与投票,也就是至少有一个知道term-2,虽然他们没有term-2的日志);
  • (c)   S5宕机,S1恢复并被选举为Leader,并且开始继续复制日志(也即是将来自term-2的index-2复制给了S3),此时term-2,index-2已经复制给了多数的服务器,但是还没有提交;
  • (d)  S1再次宕机,并且S5恢复又被选举为Leader(通过S2、S3、S4投票,因为S2、S3、S4的term=4<5,且日志条目为(term=2,index=2)并没有S5的日志条目新,所以支持选举),然后覆盖Follower中的index-2为来自term-3的index-2;(注:此时出现了,term-2中的index-2已经复制到三台服务器,还是被覆盖掉);
  • (e)  然而,如果S1在宕机之前已经将其当前任期(term-4)的条目都复制出去,然后该条目被提交(那么S5将不能赢得选举,因为S1、S2、S3的日志term=4比S5都新)。此时所有在前的条目都会被很好地提交。

  如果上述情况(c)中,term=2,index=2的日志条目被复制到大多数后,如果此时当选的S1提交了该日志条目,则后续产生的term=3,index=2会覆盖它,此时就可能会在同一个index位置先后提交一个不同的日志,这就违反了状态机安全性,产生不一致。也就是说当一个新Leader当选时,由于所有成员的日志进度不同,很可能需要继续复制前面term的日志条目,就算复制到多数服务器并且提交,还是可能被覆盖,因为前面term对应的日志条目较旧,容易使的没有这些条目的其他服务器当选Leader,此时就会覆盖这些日志条目。

  为了消除上述场景就规定Leader可以复制前面任期的日志,但是不会主动提交前面任期的日志。而是通过提交当前任期的日志,而间接地提交前面任期的日志。

4.配置变更的时候,需要保证任何时候只能有一个Leader,直接从旧的配置转化到新配置的方案是不安全的,如下图所示:

  转换过程中,server1,server2通过旧配置选出一个Leader(三台中的两台支持),server3,server4,server5通过新配置选出一个Leader(五台中的三台支持),这样在同一个term中就有两台Leader,造成不一致,为了保证安全性,配置更改必须使用两阶段方法。在 Raft 中,集群先切换到一个过渡配置,我们称之为共同一致;一旦共同一致已经被提交了,那么系统就切换到新的配置上。

过渡配置保证不会在old与new两个配置上同时产生Leader :

过渡配置是指集群具有 old + new 中所有机器的配置。

Leader会产生一个过渡配置日志,先应用到本地,然后复制给过渡配置中的所有成员。所有收到配置的成员,会直接将配置应用到本地。

处于过渡配置的成员,在Leader选举与提交日志时规则发生了变化,都要求分别得到old与new两个配置下的多数成员同意才行。比如:

old:1、2、3

new:2、3、4、5、6  (删除机器1,增加机器4、5、6)

old+new:1、2、3、4、5、6(机器是old+new所有机器)

那么处于过渡配置的成员在Leader选举与提交日志,需要满足得到 old(1、2、3)三个成员中的多数,以及new(2、3、4、5、6)五个成员中的多数。而不是old+new中六个成员的多数。明白这点非常关键。

1)从old -> old+new配置提交之前

此时还未产生new配置,因此不可能在new配置下产生Leader。

看下是否可能在old与old+new下分别产生Leader。old下要产生Leader需要old中的多数投票,old+new下要产生Leader也需要old中的多数投票,而要在old与old+new下分别产生Leader,那么在old中至少有一个成员同时投票给old中的Leader,与old+new中的Leader。根据投票规则,在一个term下只能投票一次,因此就不可能在old与old+new配置下在同一个term上产生Leader。

而在不同term下产生则不违反规则,因为新的term下产生的Leader,发送心跳给旧Leader,旧Leader就会马上变成Follower。

2)old+new提交之后

只要old+new这个配置提交,则意味着该配置以及复制给了old中的多数成员,那么在old配置中就不可能再产生一个Leader出来了。只能在old+new与new配置下产生。而在old+new与new中也不可能在同一个term下分别选举出Leader,这个证明与old与old+new配置是一样的。

若每次增加或者删除一个成员不需要过渡配置

就是说,每次配置变更只能增加或者删除一个成员。若满足则可以直接从old配置转换到new配置。

证明旧配置有N个成员,新配置有N+1个成员,在切换过程中不会再旧配置与新配置下分别产生Leader。

1)若旧配置下选举出Leader,那么需要N/2+1个成员投票。

2)新配置下,从成员数是N+1去掉以及投票的N/2+1个成员只剩下N/2个成员。

3)而新配置下要选举出Leader需要(N+1)/2+1=N/2+3/2个成员投票,但是只剩下N/2个成员可以投票,因此新配置下不可能再选举出Leader。

4)反过来也一样。

参考: http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432287.html

http://blog.csdn.net/hfty290/article/details/75331948

raft协议安全性保证的更多相关文章

  1. 理解分布式一致性:Raft协议

    理解分布式一致性:Raft协议 什么是分布式一致性 Leader选举 日志复制流程 term选举周期 timeout 选举和选举timeout 选举分裂 日志复制和心跳timeout 在分布式系统中, ...

  2. MIT-6.824 Raft协议

    摘要 raft是一种比paxos容易理解的一致性算法,实现起来比paxos简单许多.本文前部分描述算法的细节,后部分尝试探讨下该算法的原理. 算法描述 raft算法之所以简单的原因之一是它将问题分解成 ...

  3. Raft协议学习笔记

    目录 目录 1 1. 前言 1 2. 名词 1 3. 什么是分布式一致性? 3 4. Raft选举 3 4.1. 什么是Leader选举? 3 4.2. 选举的实现 4 4.3. Term和Lease ...

  4. Raft协议--中文论文介绍

    本篇博客为著名的 RAFT 一致性算法论文的中文翻译,论文名为<In search of an Understandable Consensus Algorithm (Extended Vers ...

  5. 理解Raft协议

    目录 1.Paxos算法存在的问题 2.Raft算法     2.1 复制状态机     2.2. Raft算法     2.2.1 安全性问题     2.2.2 Leader选举     2.2. ...

  6. Raft协议理解

    raft协议最关键的部分是领导选举和日志复制 日志复制 日志匹配原则:如果两个日志在相同索引位置的entry的任期号相同,那么这两个日志从头到这个索引位置之前完全相同. 日志匹配原则可以解释为如下两条 ...

  7. Raft 协议

    Paxos 存在的问题 Paxos 算法的描述偏学术化,缺失了很多细节,无法直接应用于工程领域.实际工程应用中的分布式算法大多是 Paxos 的变种,验证这些算法的正确性也成为了一个难题. 举个例子: ...

  8. raft协议

    一.Raft一致性算法 Eureka:Peer To Peer,每个节点的地位都是均等的,每个节点都可以接收写入请求,每个节点接收请求之后,进行请求打包处理,异步化延迟一点时间,将数据同步给 Eure ...

  9. 微服务架构及raft协议

    微服务架构全景图 服务注册和发现 Client side implement 调用需要维护所有调用服务的地址 有一定的技术难度,需要rpc框架支持 Server side implement 架构简单 ...

随机推荐

  1. vue组件(Vue+webpack项目实战系列之三)

    组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.特别对于大型应用开发来说,尽量组件化,并且先造好轮子库,不要重复去写组件,这会显著提升项目 ...

  2. wildfly10报错2:ID注释有错

    13:55:56,612 INFO [org.jboss.modules] (main) JBoss Modules version 1.5.1.Final 13:55:56,891 INFO [or ...

  3. PHP完成一个日历

    今天我们就用php中的数组合date 做一个日历. 先让我们回顾一下PHP里面的date . 1.时间戳:表示从计算机元年/UNIX纪年(0时区 1970/1/1 00:00:00)到当前事件的秒数. ...

  4. AVAudioSession(2):定义一个 Audio Session

    本文转自:AVAudioSession(2):定义一个 Audio Session | www.samirchen.com 本文内容主要来源于 Defining an Audio Session. A ...

  5. Java 变量类型

    局部变量 成员变量 类变量 Java局部变量 局部变量声明在方法.构造方法或者语句块中: 局部变量在方法.构造方法.或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁: 访问修饰符不能用于局 ...

  6. java网络编程之socket(2)

    异步处理多客户端连接服务端 上篇讲到的是服务端接收一个客户端的请求之后就结束了,不能再接收其他客户端的请求了,实际情况上我们希望服务端能够处理来自不同用户的请求. 想到这里,或许我们可以用一个死循环, ...

  7. php7.0版本不再以类名命名构造函数

    <?php class Car { var $color = "add"; function Car($color="green") { $this-&g ...

  8. English Learning - Vampire bats

    " Vampire bats are very adaptable." Bambi said. And when their roosts are disrupted or the ...

  9. es6的一些知识点

    es6的一些知识点 前言:es6(ECMAscript2015)标准 let.const.var的一些区别 let.const 块级作用域.全局作用域.函数作用域 var 全局作用域.函数作用域 变量 ...

  10. java中变量赋值的理解

    1.当赋值的值超出声明变量的范围时候,会报错! byte a =200 //会报错,因超出范围. byte a =(byte)200;//进行一个强制转换,就不会报错,不过会超出范围,超出部分会从头开 ...