介绍

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

Raft独有特性:

  1. 强领导者:日志条目只从领导者发送给其他的服务器
  2. 领导选举:Raft 算法使用一个随机计时器来选举领导者
  3. 关系调整:Raft 使用一种共同一致的方法来处理集群成员变换的问题

Raft一致性算法

Raft 基础

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

  • 领导者:领导人处理所有的客户端请求(如果一个客户端和跟随者联系,那么跟随者会把请求重定向给领导人)。
  • 跟随者:他们不会发送任何请求,只是简单的响应来自领导者或者候选人的请求。
  • 候选人,选举新领导人时使用

一些相关状态

状态 所有服务器上持久存在的
currentTerm 服务器最后一次知道的任期号(初始化为 0,持续递增)
votedFor 在当前获得选票的候选人的 Id
log[] 日志条目集;每一个条目包含一个用户状态机执行的指令,和收到时的任期号
状态 所有服务器上经常变的
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;
}

日志复制

日志遵循一下特性:

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

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

附加日志 RPC

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

参数 解释
term 领导人的任期号
leaderId 领导人的 Id,以便于跟随者重定向请求
prevLogIndex 新的日志条目紧随之前的索引值
prevLogTerm prevLogIndex 条目的任期号
entries[] 准备存储的日志条目(表示心跳时为空;一次性发送多个是为了提高效率)
leaderCommit 领导人已经提交的日志的索引值
返回值 解释
term 当前的任期号,用于领导人去更新自己
success 跟随者包含了匹配上 prevLogIndex 和 prevLogTerm 的日志时为真
//接收者实现:
//Leader肯定有最大的term
if (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
  5. 客户端交互:只有leader和客户端交互
  6. 更改配置

Raft论文概述的更多相关文章

  1. 【转】分布式一致性算法:Raft 算法(Raft 论文翻译)

    编者按:这篇文章来自简书的一个位博主Jeffbond,读了好几遍,翻译的质量比较高,原文链接:分布式一致性算法:Raft 算法(Raft 论文翻译),版权一切归原译者. 同时,第6部分的集群成员变更读 ...

  2. raft 论文

    raft 论文,摘自  http://www.infoq.com/cn/articles/raft-paper raft动画:https://raft.github.io/ raft说明动画:

  3. Raft论文《 In Search of an Understandable Consensus Algorithm (Extended Version) 》研读

    Raft 论文研读 说明:本文为论文 < In Search of an Understandable Consensus Algorithm (Extended Version) > 的 ...

  4. Raft论文的一些问题

    抛些问题出来,真正解释了这些问题才算理解了论文.:) 1. 什么是复制状态机 2. Raft vs Paxos 3. Raft的设计目标understandability,为达到设计目标在做设计时如何 ...

  5. Raft论文学习笔记

    先附上论文链接  https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf 最近在自学MIT的6.824分布式课程,找到两个比较好的githu ...

  6. 从raft论文出发

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

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

    Raft 算法是可以用来替代 Paxos 算法的分布式一致性算法,而且 raft 算法比 Paxos 算法更易懂且更容易实现.本文对 raft 论文进行翻译,希望能有助于读者更方便地理解 raft 的 ...

  8. Raft概述

    Raft 1. 概述 Raft是一种一致性(共识)算法,相比Paxos,Raft更容易理解和实现,它将分布式一致性问题分解成多个子问题,Leader选举(Leader election).日志复制(L ...

  9. 学习Raft算法的笔记

    Raft是一种为了管理日志复制的一致性算法.它提供了和Paxos算法相同的功能和性能,但是它的算法结构和Paxos不同,使得Raft算法更加容易理解并且更容易构建实际的系统.为了提升可理解性,Raft ...

随机推荐

  1. AGC050B Three Coins

    做的时候有思考到是否能转化成移动点问题,但是没有清晰的把他解释出来. NOIP的时候也一样,T3也有考虑到是否能转为差分,但是也没有清晰的写出来. 自己做题的时候应尽量保证草稿纸和思绪的清晰,而不是在 ...

  2. NOI2021 去不了记

    没错,由于某些 zszz 的原因,我是真的去不了了(指去不了 ZJ) Day -11 ~ -7 - 2021.7.12 - 2021.7.16 令人自闭的 ISIJ 终于结束了----From ycx ...

  3. Codeforces 840E - In a Trap(树分块+trie)

    Codeforces 题面传送门 & 洛谷题面传送门 一道非常精彩,同时也很经典的题目.和这场的 C 一样经典 首先看到这个数据范围先猜正解复杂度:\(n\) 级别大于 \(q\),所以大概是 ...

  4. Python基础之流程控制if判断

    目录 1. 语法 1.1 if语句 1.2 if...else 1.3 if...elif...else 2. if的嵌套 3. if...else语句的练习 1. 语法 1.1 if语句 最简单的i ...

  5. Netty | 第1章 Java NIO 网络编程《Netty In Action》

    目录 前言 1. Java 网络编程 1.1 Javs NIO 基本介绍 1.2 缓冲区 Buffer 1.2 通道 Channel 1.3 选择器 Selector 1.4 NIO 非阻塞网络编程原 ...

  6. 巩固java第六天

    巩固内容: HTML 空元素 没有内容的 HTML 元素被称为空元素.空元素是在开始标签中关闭的. <br> 就是没有关闭标签的空元素(<br> 标签定义换行). 在 XHTM ...

  7. Vue 之keep-alive的使用,实现页面缓存

    什么是keep-alive 有时候我们不希望组件被重新渲染影响使用体验: 或者处于性能考虑,避免多次重复渲染降低性能.而是希望组件可以缓存下来,维持当前的状态.这时候就需要用到keep-alive组件 ...

  8. CVTE第二次笔试

    选择瞎答得,直接编程题目 1. 使用递归将字符串中的数字去并按顺序打印 输入例  adfsafsfs123123eogie09789 输出例 123123 09789 #include<iost ...

  9. 内存管理——new delete expression

    C++申请释放内存的方法与详情表 调用情况 1.new expression new表达式在申请内存过程中都发生了什么? 编译器将new这个分解为下面的主要3步代码,①首先调用operator new ...

  10. 双向循环链表模板类(C++)

    双向链表又称为双链表,使用双向链表的目的是为了解决在链表中访问直接前驱和后继的问题.其设置前驱后继指针的目的,就是为了节省其时间开销,也就是用空间换时间. 在双向链表的每个节点中应有两个链接指针作为它 ...