上一篇文章:Raft算法之Leader选举
  之前说完了Raft算法中的Leader选举过程,本文将在上一篇文章的基础上说明日志复制。

Raft算法之日志复制

  先看以下日志所包含的基本内容:

  1. 可以被复制状态机执行的命令
  2. 任期号 :创建该日志时Leader所处的当前任期号
  3. 索引号 :整数,用于标识日志所在的位置

日志的状态分为两种:未被提交,已被提交(日志为安全的,不会被删除或覆盖)。

1 正常情况

  • Leader接收到由客户端发送的请求(请求中包含可以被复制状态机执行的命令)时,Leader将会把该请求作为新的内容添加到日志中(任期号为当前Leader所处的任期号,索引号为当前Leader本地存储的日志集合中的日志的最高索引号加1)。
    • Leader在当前任期内最多只能创建一个给定索引号的日志(即不可能在一个任期内创建两个以上的具有相同索引的日志条目)
  • 然后将该日志通过AppendEntries RPC消息发送到网络中其他的服务器(以下简称Follower),从而复制该日志。
  • 在网络中Follower接收到该日志消息后则会返回复制成功的回复。
  • Leader接收到网络中大部分的Follower的成功复制的回复之后,Leader便认为该日志可以被提交。此时Leader将会同时做三件事:
  1. 将该日志应用到Leader本地的复制状态机
  2. 向所有Follower发送消息通知所有接收到该日志的Follower将该日志进行提交,然后应用到各自本地的复制状态机
  3. 将执行结果通知客户端

  当该日志消息成功在网络中大部分Follower本地的复制状态机执行过后,则可认为该日志已被提交。在当前日志被提交的过程中,如果Leader先前的某些日志还没有被提交,则将会一同提交。
  而网络中有些Follower可能由于网络状态原因反应缓慢或者崩溃,那么Leader将会无限次地尝试重复发送AppendEntries RPC消息到该Follower。直到成功为止。

1.1 日志的一致性检查

  在上面,我们说了Follower在接收到AppendEntries RPC消息后则会返回复制成功的回复。实际上在接收到消息后会首先进行日志的一致性检查(正常情况下LeaderFollower的日志会保持一致,所以一致性检查不会失败),一致性检查内容如下:

  • Leader创建AppendEntries RPC消息时,消息中将会包含当前日志之前日志条目的任期号与索引号。
  • Follower在接受到AppendEntries RPC消息后,将会检查之前日志的任期号与索引号是否可以匹配到
    • 如果匹配到则说明和Leader之前的日志是保持一致的。
    • 如果没有匹配则会拒绝AppendEntries RPC消息.

  一致性检查是一个归纳的过程。正常情况下,网络中第一条日志一定满足日志的一致性检查,然后第二条日志中包含第一条日志的任期号与索引号,所以只要LeaderFollower的第一条日志保持一致,那么第二条日志也会满足一致性检查,从而之后的每一条日志都会满足一致性检查。

  从而得出了日志匹配属性:

  • 如果两个不同的日志实体具有相同的索引和任期号,那么他们存储有相同的命令。
  • 如果两个不同的日志实体具有相同的索引和任期号,则所有先前条目中的日志都相同。(由一致性检查结果得出)

2 特殊情况

  而网络不可能一直处于正常情况。因为Leader或者某个Follower有可能会崩溃,从而导致日志不能一直保持一致。因此存在以下三种情况:

  1. Follower缺失当前Leader上存在的日志条目。

  2. Follower存在当前Leader不存在的日志条目。(比如旧的Leader仅仅将AppendEntries RPC消息发送到一部分Follower就崩溃掉,然后新的当选Leader的服务器恰好是没有收到该AppendEntries RPC消息的服务器)

  3. 或者Follower即缺失当前Leader上存在的日志条目,也存在当前Leader不存在的日志条目

    图1

  图中最上方是日志的索引号(1-12),每个方块代表一条日志信息,方块内数字代表该日志所处的任期号。图中当前Leader(图中最上方一行日志代表当前Leader日志)处于任期号为8的时刻。以此图说明以上三种情况存在的原因:

  • Follower a,b(Follower崩溃没有接收到Leader发送的AppendEntries RPC消息)满足以上说明的第一种情况。
  • (Followerc在任期为6的时刻,Followerd在任期为7的时刻)为Leader,但没有完全完成日志的发送便崩溃了.满足以上说明的第三种情况。
  • Followere在任期为4的时刻,Followerf在任期为2,3的时刻为Leader,,但没有完全完成日志的发送便崩溃了,同时在其他服务器当选Leader时刻也没有接收到新的Leader发送的AppendEntries RPC消息,满足第三种情况。

2.1 日志不一致的解决方案

  Leader通过强迫Follower的日志重复自己的日志来处理不一致之处。这意味着Follower日志中的冲突日志将被Leader日志中的条目覆盖。因此Leader必须找到与Follower最开始日志发生冲突的位置,然后删除掉Follower上所有与Leader发生冲突的日志。然后将自己的日志发送给Follower以解决冲突。
Leader不会删除或覆盖自己本地的日志条目

  这些步骤从之前说到的日志的一致性检查开始。

  • 当发生日志冲突时,Follower将会拒绝由Leader发送的AppendEntries RPC消息,并返回一个响应消息告知Leader日志发生了冲突。

  • Leader为每一个Follower维护一个nextIndex值。该值用于确定需要发送给该Follower的下一条日志的位置索引。(该值在当前服务器成功当选Leader后会重置为本地日志的最后一条索引号+1)

  • Leader了解到日志发生冲突之后,便递减nextIndex值。并重新发送AppendEntries RPC到该Follower。并不断重复这个过程,一直到Follower接受该消息。

  • 一旦Follower接受了AppendEntries RPC消息,Leader则根据nextIndex值可以确定发生冲突的位置,从而强迫Follower的日志重复自己的日志以解决冲突问题。

    图2

  • 情况a: 如图,服务器S1在任期为2的时刻仅将日志<index:2,term:2>发送到了服务器S2便崩溃掉。

  • 情况b: 服务器S5在任期为3的时刻当选Leader(S5的计时器率先超时,递增任期号为3因此高于服务器S3,S4,可以当选Leader),但没来得及发送日志便崩溃掉。

  • 情况c: 服务器S1在任期为4的时刻再次当选Leader(S1重启时,任期仍然为2,收到新的LeaderS5发送的心跳信息后更新任期为3,而在LeaderS5崩溃后,服务器S1为第一个计时器超时的,因此发起投票,任期更新为4,大于网络中其他服务器任期,成功当选Leader),同时将日志<index:2,term:2>发送到了服务器S2,S3,但还没有通知服务器对日志进行提交便崩溃掉。

  • 情况d: 情况(a->d)如果在任期为2时服务器S1作为Leader崩溃掉,S5在任期为3的时刻当选Leader,由于日志<index:2,term:2>还没有被复制到大部分服务器上,并没有被提交,所以S5可以通过自己的日志<index:2,term:3>覆盖掉日志<index:2,term:2>

  • 情况e: 情况(a->e)而如果在任期为2时服务器S1作为Leader,并将<index:2,term:2>发送到S2,S3,成功复制到大多数成员服务器上。并且成功提交了该日志,那么即便S1崩溃掉,S5也无法成功当选Leader,因为S5不具备网络中最新的已被提交的日志条目(这里说明了上一篇文章Raft算法之Leader选举中选举Leader的要求中没有介绍的那一点要求).

2.2 选举Leader的对日志的要求

  • Raft使用投票程序来防止Candidate赢得选举,除非其日志中包含所有已提交的日志条目。
  • Candidate必须联系集群的大多数才能被选举,这意味着每个提交的条目都必须存在于其中至少一台服务器中。如果Candidate的日志至少与该多数服务器日志中的日志一样最新(以下精确定义了最新),则它将保存所有已提交的条目。
  • Raft通过比较日志中最后一个条目的索引和任期来确定两个日志中哪个是最新的。如果日志中的最后一个条目具有不同的任期,则带有较新任期的日志是最新的。如果日志以相同的任期结尾,则以索引更大的日志为准。

  解决方案的优化
  在Follower拒绝AppendEntries RPC消息时,可以选择将发生冲突的日志的任期与该任期内的第一条日志索引包含在拒绝消息中返回给Leader,从而使得Leader可以快速定位到发生冲突的位置。有了这些信息,Leader可以递减nextIndex来绕过该任期中所有冲突的条目。每个具有冲突日志条目所处的任期都需要一个AppendEntries RPC消息,而不是每个日志条目都需要一个AppendEntries RPC消息。

3 日志复制安全性

Raft保证任何时刻这里的每一条属性都成立

  • Leader只追加特性:Leader从不覆盖或删除它的日志条目,只追加新的。
  • 日志匹配: 如果两个日志包含的实体具有相同的索引和任期,那么直到给定索引为止,所有条目中的日志都是相同的。
  • Leader完整性:如果一个日志提示在给定的任期内被提交,那么该条目将出现在所有任期更高的领导者的日志中.
  • 状态机安全:如果服务器应用一条给定索引的日志实体到它的状态机,那么没有其他服务器可以应用一条不同的日志到相同的索引位置。

3.1 Leader完整性证明

  假设Leader完整性不成立,然后证明是矛盾的。
  假设任期为TLeader提交了当前任期的日志条目,但是该日志没有被任期高于T的任期为U的未来的新的Leader所存储。

  1. 被提交任期为T的日志必须不存在于将要选举的任期为ULeader的复制状态机中(因为Leader从不覆盖或删除它的日志条目)。
  2. 任期为TLeader将日志复制到集群中的大部分成员本地。并且任期为ULeader在选举阶段接收到集群中大部分成员的投票,因此至少集群中有一个成员(以下称为投票者)即接收到来自任期为TLeader发送的日志,也为任期为ULeader投了票。所以该投票者是证明矛盾的关键所在。
  3. 投票者必须在为任期为ULeader投票之前将任期为TLeader的发送的日志提交。不然投票者将会拒绝任期为TLeaderAppendEntries PRC请求(因为一旦接收到任期为ULeader投票请求,投票者的任期将会高于T)。
  4. 投票者当为任期为ULeader投票时,将会一直存储该日志条目。假设在任期为TU之间的每一个Leader都包含该日志条目(Leader从不删除日志条目,而Follower仅在与Leader冲突时才删除条目)。
  5. 投票者为任期为ULeader投票,所以任期为ULeader日志必须至少和投票者的日志一样新。这将导致产生两个矛盾之中的一个矛盾。
  6. 首先,如果投票者和任期为ULeader具有相同的最新的日志任期。那么任期为ULeader的日志至少和投票者的日志一样长。所以任期为ULeader的日志将包含投票者所有的日志。这是一个矛盾,因为之前假设的投票者包含被提交的任期为T的日志,而任期为ULeader不包含。
  7. 否则,任期为ULeader的最后一条日志的任期号必须大于投票者的最后一条日志的任期号。而且,它比T大,因为投票者的上一个日志任期号至少为T(它包含任期T中的所有已提交的条目)。创建任期为ULeader的最后一个日志条目的较早的Leader必须在其日志中(通过假设)包含已提交的条目。然后,通过日志匹配属性,任期为ULeader的日志还必须包含已提交的条目,这是矛盾的。
  8. 这样就证明了矛盾,因此所有任期大于TLeader都必须包含所有任期为T的被提交的日志。
  9. 日志匹配属性保证未来的Leader还将包含间接提交的日志条目。

下一篇文章:Raft算法之成员关系变化



algorithm      algorithm Raft

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!