对Raft共识算法的一些理解

文章目录

  • 为什么需要共识算法
  • Paxos
  • Raft
    • 选举Leader (Leader election)
    • 日志副本 (Log replication)
    • 安全(Safety)
      • 选举限制 (Election restriction)
      • 提交前一term的日志条目 (Committing entries from previous terms)
  • Cluster membership changes
  • Log compaction
  • linearizable semantics
  • 参考资料

paper: In Search of an Understandable Consensus Algorithm (Extended Version)

为什么需要共识算法

为了保证服务的稳定性(解决单点故障问题),人们提出了副本技术(replication)。但是副本之间需要一个中心服务器进行协调(比如GFS和MapReduce的master server),那么单点故障只是转移到了中心服务器,并没有得到彻底解决。

于是人们又提出了使用多个中心服务器用来容错,本质上又是副本技术,那么如何保证一致性呢?这里没有使用更高一层的中心服务器,而是提出了RSM (replicated state machine),也就是把单个服务器当做一个状态机,所有的输入会引起状态的转移,同时需要保证服务器的所有操作都是确定的(比如获取当前时间、产生随机数等就是不确定行为,产生的结果因机器等因素而异),否则不同服务器即使都接受相同的输入,得到的状态也会不一致。

一般来说,人们通过维护一个日志,记录所有的输入,对于不确定指令,只在一台服务器上执行,然后将其结果当做输入记录到日志中,其他服务器按顺序执行日志中的命令,就可以保证所有的服务器保持相同的状态。

那么核心问题就是如何保证所有机器都维护相同的日志,这就是共识算法需要做的事情了。

Paxos

Paxos提出的比较早,在长达十多年的时间里一直是主流。但是非常难以理解,而且实现方面可能存在问题。
因此Diego Ongaro 和 John Ousterhout提出了Raft。

Raft

在Raft中,节点有三种状态,LeaderFollowerCandidate,三种状态之间的转换关系如下如所示。
对Raft共识算法的一些理解_第1张图片
在正常情况下,只有一个Leader,其他所有节点都是FollowerCandidate状态是为了选举新的Leader的临时状态。Follower只回应LeaderCandidate的消息,Leader处理所有来自client的消息。
在Raft中,时间被分为了term,有连续的整数标识,每个term开始时都进行选举,Raft保证每个term最多只有一个Leader

选举Leader (Leader election)

election timeout:选举时间,在Raft中,Leader周期性的向Follower发送心跳包,如果Follower在选举时间内没有收到来自Leader或者Candidate的心跳包,那么自己就变成Candidate去竞选Leader,同时term加1。

Candidate状态会一直持续,直到以下三种情况发生:

  1. Candidate赢了本次选举:收到了大多数的投票,Candidate变成Leader,开始向其它节点发送心跳包,其它节点变成Follower.
  2. Candidate收到了某节点的心跳包,如果该心跳包包含的term不小于自己的term,那么该Candidate变成Follower,否则忽略该心跳包。
  3. 没有任何Candidate赢得选举,那么增加term重新开始新一轮的选举。

为了避免反复进入第三种情况,Raft使用了随机的election timeout(150-300ms),因此各个节点进入Follower的时间不完全相同(发起投票的时间不同),大大降低了进入第三种情况的可能。

日志副本 (Log replication)

Leader收到client的请求后,先生成一个日志条目(log entry),该条目都包含一个当前term和一个index(日志中的位置),然后将该条目发给Follower。当日志条目复制到大多数节点后,日志被Leader标记为committed,此时返回结果给client。

在日志中,Raft保证两个属性:

  1. 拥有相同termindex的日志条目包含相同的请求命令。
  2. 拥有相同termindex的日志条目之前的日志条目完全相同。

由于日志条目只由Leader生成,Leader保证每个term中对于一个index只生成一个日志条目,因此属性一显然满足。
对于属性二,当LeaderFollower发送日志条目时,会附带前一个日志条目的termindex,如果Follower的日志条目与之不一致,就拒绝添加新的日志条目,并向Leader索要前一个日志条目,直到满足termindex,然后将之后的日志从Leader复制到Follower中。

安全(Safety)

以上还不足以保证所有的节点执行相同的操作,比如当一个Follower掉线重新上线后被选为了Leader,由于该Leader丢失了很多日志,后续可能会把前一个Leader标记为Committed的日志条目覆盖掉,导致不同的节点执行了不同的操作。

Raft对Leader选举添加了一个限制来解决这一问题:保证任何termLeader包含前一term中被标记为Committed的日志条目。

选举限制 (Election restriction)

在投票过程中,Candidate需要附带上自己的日志中最后一个条目的termindex,如果投票者的日志比Candidate的日志新,那么就拒绝投票。因此如果某Candidate得到了大多数节点的投票,就可以保证该Candidate的日志包含了前一个term中被标记为Committed的所有日志条目。

这里需要说明一下,由于只有被大多数节点备份的日志条目才会被标记为Committed,而Candidate又需要得到大多数节点的投票,这两个大多数节点中至少有一个节点是重复的,因此可以保证满足该限制:任何termLeader包含前一term中被标记为Committed的日志条目

提交前一term的日志条目 (Committing entries from previous terms)

Raft如何处理前一个term中的日志条目? 如下图所示,S1到S5是五台服务器,每个方格中的数字是term,每个方格代表clients发起的一个请求命令。
对Raft共识算法的一些理解_第2张图片
在a中,S1是Leader,正在将日志复制到Follower,此时S1出现了故障;到了b,S5被选为了Leader,并接受了clients发起的一个命令,还没开始复制就挂掉了;到了c,S1被重新选为Leader,继续复制未完成的term2中的日志,此时该日志已经被大多数节点接受,不应该再被撤销了,然而,在S1准备commit该日志的时候,S1挂了;到了d,S5被选为新的Leader,复制日志过程中将term2的日志覆盖了;在e中,如果S1没有去管term2,直接去复制并commit term4的日志,那么当term4被commit之后,term2的日志也会被间接commit,S5就不会被选为Leader了,不会导致覆盖发生。

有点难以理解。。。我的理解是:在c中,term2的命令已经被大多数节点记录到日志中,但是此时Leader是否执行、是否返回结果给client是未知的,因此我们只能假设已经执行并且返回了结果(否则会导致不一致),所以term2的日志绝对不能再被覆盖。

Raft对于这种问题的解决方案是:Leader不去直接commit前一个term的日志,而是去commit当前term的日志,然后通过前面提到的日志副本的属性二保证前面term的日志会被间接commit。

Cluster membership changes

TODO

Log compaction

TODO

linearizable semantics

TODO

参考资料

  1. https://www.zhihu.com/question/36648084/answer/82332860
  2. https://zhuanlan.zhihu.com/p/46531628

深入阅读:

  • https://mp.weixin.qq.com/s?__biz=MjM5MDE0Mjc4MA==&mid=2650997287&idx=1&sn=4b3ef76bb90c2e28e259802866dc934e&chksm=bdbefa748ac97362184c485dd93ef821c82662f4d64359f0ba9d3cfbb387a593186d947c7dee&scene=21&key=d3935a5e90e39d9987fe240afa271d26503fbe790939c18bcee015#wechat_redirect
  • https://www.zhihu.com/question/29597104
  • https://www.zhihu.com/question/30026369

你可能感兴趣的:(the,evening,paper,分布式系统,公式算法,分布式,raft,RSM)