Raft 一致性算法

Posted 李博洋的杂货铺

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Raft 一致性算法相关的知识,希望对你有一定的参考价值。

介绍

Raft是一种为了管理复制日志的一致性算法。为了提升可理解性,Raft 将一致性算法分解成了几个关键模块,例如领导人选举、日志复制和安全性。同时它通过实施一个更强的一致性来减少需要考虑的状态的数量。同时,raft还提供了集群变更的应对方法。

Raft独有特性:

  1. 强领导者:日志条目只从领导者发送给其他的服务器

  2. 领导选举:Raft 算法使用一个随机计时器来选举领导者

  3. 关系调整:Raft 使用一种共同一致的方法来处理集群成员变换的问题

Raft一致性算法

Raft 基础

一个 Raft 集群包含若干个服务器节点;通常是 5 个,这允许整个系统容忍 2 个节点的失效。

  • 领导者:领导人处理所有的客户端请求(如果一个客户端和跟随者联系,那么跟随者会把请求重定向给领导人)。

  • 跟随者:他们不会发送任何请求,只是简单的响应来自领导者或者候选人的请求。

  • 候选人,选举新领导人时使用 

一些相关状态

状态 所有服务器上持久存在的
currentTerm 服务器最后一次知道的任期号(初始化为 0,持续递增)
votedFor 在当前获得选票的候选人的 Id
log[] 日志条目集;每一个条目包含一个用户状态机执行的指令,和收到时的任期号
  • currentTerm可能大于最新日志Term

状态 所有服务器上经常变的
commitIndex 已知的最大的已经被提交的日志条目的索引值
lastApplied 最后被应用到状态机的日志条目索引值(初始化为 0,持续递增)
状态 在领导人里经常改变的 (选举后重新初始化)
nextIndex[] 对于每一个服务器,需要发送给他的下一个日志条目的索引值(初始化为领导人最后索引值加一)
matchIndex[] 对于每一个服务器,已经复制给他的日志的最高索引值

所有服务器需遵守的规则

所有服务器:

if(commitIndex > lastApplied){
    那么就 lastApplied 加一,并把log[lastApplied]应用到状态机中;
}if(T > currentTerm){
     currentTerm 等于 T,并切换状态为跟随者
}

跟随者

  1. 响应来自候选人和领导者的请求;

  2. 如果在超过选举超时时间的情况之前都没有收到领导人的心跳,或者是候选人请求投票的,就自己变成候选人

候选人

  1. 在转变成候选人后就立即开始选举过程

    1. 自增当前的任期号(currentTerm)

    2. 给自己投票

    3. 重置选举超时计时器

    4. 发送请求投票的 RPC 给其他所有服务器

  2. 如果接收到大多数服务器的选票,那么就变成领导人

  3. 如果接收到来自新的领导人的附加日志 RPC,转变成跟随者

  4. 如果选举过程超时,再次发起一轮选举

领导人

  1. 一旦成为领导人:发送空的附加日志 RPC(心跳)给其他所有的服务器;在一定的空余时间之后不停的重复发送,以阻止跟随者超时

  2. 如果接收到来自客户端的请求:附加条目到本地日志中,在条目被应用到状态机后响应客户端

  3. 如果对于一个跟随者,最后日志条目的索引值大于等于nextIndex,发送从 nextIndex 开始的所有日志条目

    1. 如果成功:更新相应跟随者的 nextIndex 和 matchIndex

    2. 如果因为日志不一致而失败,减少 nextIndex 重试

领导人选举

如果跟随者一段时间没有接受到Leader的任何消息,开始超时选举。竞选者增加当前term号,向其他节点请求选票。他保持当前状态,直到以下三种情况发生。

  1. 赢得本次选举

  2. 其它服务器赢得选举

  3. 选举超时

请求投票 RPC

参数 解释
term 候选人的任期号
candidateId 请求选票的候选人的 Id
lastLogIndex 候选人的最后日志条目的索引值
lastLogTerm 候选人最后日志条目的任期号
返回值 解释
term 当前任期号,以便于候选人去更新自己的任期号
voteGranted 候选人赢得了此张选票时为真

接收者实现:

if(term < currentTerm){    return false;
}//先来先投票原则,并且只投票给大多数if(voteFor为空 || 参与者就是接收者){    if(候选人的日志和自己的一样新)        return true;
}

日志复制

Raft 一致性算法

日志遵循一下特性:

  1. 如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们存储了相同的指令

  2. 如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们之前的所有日志条目也全部相同

第一个特性来自这样的一个事实,领导人最多在一个任期里在指定的一个日志索引位置创建一条日志条目,同时日志条目在日志中的位置也从来不会改变。第二个特性由附加日志 RPC 的一个简单的一致性检查所保证。在发送附加日志 RPC 的时候,领导人会把新的日志条目紧接着之前的条目的索引位置和任期号包含在里面。如果跟随者在它的日志中找不到包含相同索引位置和任期号的条目,那么他就会拒绝接收新的日志条目。

附加日志 RPC

由领导人负责调用来复制日志指令;也会用作heartbeat

参数 解释
term 领导人的任期号
leaderId 领导人的 Id,以便于跟随者重定向请求
prevLogIndex 新的日志条目紧随之前的索引值
prevLogTerm prevLogIndex 条目的任期号
entries[] 准备存储的日志条目(表示心跳时为空;一次性发送多个是为了提高效率)
leaderCommit 领导人已经提交的日志的索引值
返回值 解释
term 当前的任期号,用于领导人去更新自己
success 跟随者包含了匹配上 prevLogIndex 和 prevLogTerm 的日志时为真
//接收者实现://Leader肯定有最大的termif (term < currentTerm)    return false;if(prevLogIndex位置上的term和prevLogTerm不匹配){
    删除这一条之后所有的日志;    return false;
}
附加任何在已有的日志中不存在的条目;if(leaderCommit > commitIndex){
    leaderCommit =  min(leaderCommit, 新日志条目索引值);
}return true;

安全性

选举限制

Raft 使用投票的方式来阻止候选人赢得选举除非这个候选人包含了所有已经提交的日志条目。

  1. 如果两份日志最后的条目的任期号不同,那么任期号大的日志更加新。

  2. 如果两份日志最后的条目任期号相同,那么日志比较长的那个就更加新。

提交之前任期内的日志条目

主要是对commit的定义:只有它自己提交当前term号的操作才能将之前的日志看作是真正的可以提交。

一个领导人不能断定一个之前任期里的日志条目被保存到大多数服务器上的时候就一定已经提交了。

  1. (a) 中,S1 是领导者,部分的复制了索引位置 2 的日志条目。

  2. (b) 中,S1 崩溃了,然后 S5 在任期 3 里通过 S3、S4 和自己的选票赢得选举,然后从客户端接收了一条不一样的日志条目放在了索引 2 处。

  3. (c),S5 又崩溃了;S1 重新启动,选举成功,开始复制日志。在这时,来自任期 2 的那条日志已经被复制到了集群中的大多数机器上,但是还没有被提交。

  4. 如果 S1 在 (d) 中又崩溃了,S5 可以重新被选举成功(通过来自 S2,S3 和 S4 的选票),然后覆盖了他们在索引 2 处的日志。但是,在崩溃之前,如果 S1 在自己的任期里复制了日志条目到大多数机器上

  5. 如 (e) 中,然后这个条目就会被提交(S5 就不可能选举成功)。 在这个时候,之前的所有日志就会被正常提交处理。

图中展示了一种情况,一条已经被存储到大多数节点上的未提交的老日志条目(这不是废话吗?),也依然有可能会被未来的领导人覆盖掉。

只有它自己提交当前term的操作才能看作是真正的提交。(leader只有提交了当前term号的日志后才能将之前的日志应用到状态机)

成员组变更

成员组变更的难点

  1. 成员组正常Paxos日志同步服务不中断

  2. 任何情况下宕机都能够保证存活的多数派成员间能够选举leader

  3. 不会出现1个以上的多数派选出大于1个leader的情况

基本思路

  1. “旧朝代”的多数派成员对“旧朝代结束”这件事达成一致,达成一致后旧成员组不再投票

  2. “新朝代”的多数派成员对“新朝代开启”这件事达成一致,达成一致后新成员组开始投票

上面的思路可以满足难点3,但是不能满足难点1,2。比如Pa执行成功后,在Pb执行成功之前:没有成员组可以投票,服务会中断;如果集群宕机重启,新的成员组的各个成员由于还未对新成员组达成一致,而无法选出leader。

Joint-Consensus

通用成员组变更方法--Joint-Consensus

  • 变更操作

  1. 成员变更操作前,C(old)的多数派中持久化的成员组为[[C(old)]]

  2. 成员变更操作由leader执行,leader收到命令后,将成员组[[C(old),C(new)]]发送给C(old)∪C(new)的所有成员,在此之后新的日志同步需要保证得到C(old)和C(new)两个多数派的确认

  3. leader收到C(old)和C(new)两个多数派确认后,将成员组[[C(new)]]发送给C(new)的所有成员,收到C(new)多数派确认后,表示成员变更成功,后续的日志只要得到C(new)多数派确认即可

  • 投票规则

  1. 持有[[C(old),C(new)]]的候选人要得到C(old)和C(new)两个多数派都确认,才能当选leader

  2. 持有[[C(old)]]的候选人要得到C(old)多数派确认,才能当选leader

  3. 持有[[C(new)]]的候选人要得到C(new)多数派确认,才能当选leader

总结

  1. 领导者选举

  2. 普通附加操作

  3. 安全性和一致性

  4. 平衡旧领导者

出现网络分区,通过Term号使旧领导者变为follower

  1. 客户端交互

只有leader和客户端交互

  1. 更改配置


动画演示

Raft协议详解——对集群变更解释很好

raft论文

对论文的补充


以上是关于Raft 一致性算法的主要内容,如果未能解决你的问题,请参考以下文章

RAFT算法深入(上)

分布式一致性算法:Raft 算法(Raft 论文翻译)

分布式共识算法——Raft算法(图解)

分布式共识算法——Raft算法(图解)

分布式一致性算法:Raft 算法

Raft 一致性算法