看动画轻松学会 Raft 算法
由于 Paxos 算法过于晦涩难懂且难以实现,Diego Ongaro 提出了一种更易于理解和实现并能等价于 Paxos 算法的共识算法 - Raft 算法。
因为 Raft 算法清晰易懂越来越多的开源项目尝试引入 Raft 算法来解决分布式一致性问题。在分布式存储领域基于 Raft 算法构建的项目百花齐放,欣欣向荣。
介绍 Raft 算法的文章早已是汗牛充栋,本文先介绍两个非常优秀的网站:
The Secret Lives of Data-CN 以图文方式介绍 Raft 算法,是非常好的入门材料。将其阅读完后您大概率已经了解了 Raft 算法,如果您仍有疑问可以回来继续阅读本文。
既然您已经回来继续阅读,相信您已经了解 Raft 算法中的Leader 选举、日志复制等基本概念, 但仍有部分疑惑。没关系, 接下来我们会解决这些问题。
Raft Scope 是 Raft 官方提供的互动式演示程序,它展示了 Raft 集群的工作状态。您可以用它模拟节点宕机、心跳超时等各种情况。有了 Raft Scope 我们可以亲自“动手” 观察 Raft 集群是如何工作、如何处理各种故障的。
遗憾的是这个程序几乎没有任何说明非常难以上手。本文接下来将先介绍如何使用 Raft Scope 然后用它模拟几种 Raft 集群工作中会遭遇的典型状况。
Raft Scope 说明

可以看到 Raft Scope 界面由三部分组成。
最下方有两个滑块:上面的是进度条您可以拖动它回看刚刚发生过事件,下面的是变速器滑块越靠左系统运行越慢。

左上角部分是一个由 5 个节点组成的 Raft 集群,每个圆圈代表集群中的一个节点。点击节点可以看到它的状态。对话框的右下角有一些按钮,我们可以点击按钮模拟各种状况。我们直接右键点击节点也可以看到这些按钮


这些按钮的功能是:
- stop: 节点停机
- resume: 启动停机的节点
- restart: 将节点立即重启
- time out: 模拟心跳超时,点击按钮后相应节点会认为 Leader 发生了心跳超时。
- request: 向集群提交新的数据
节点中间的数字是节点当前的任期号(Term), 节点的颜色似乎同样是用来表示任期的。
节点可能处于 Follower、Candidate 或者 Leader 状态。

S2 处于 Candidate 状态,实心原点表示它现在收到的投票。图中的两个原点表示收到了 S2 和 S4 的投票,这 5 个小圆点和集群中节点的位置是对应的,左下角的小圆点表示 S4, 最上面的小圆点表示 S1。在集群选举过程中节点外的动态边框表示 Election Timeout。

黑色实心边框表示 S5 是 Leader。Follower 外面的边框表示 HeartBeat 超时倒计时。

右上角的表格表示各节点的日志,每行表示一个节点。
表格最上面的数字是日志的序号(Log Index)。Log Index 是一个自增且连续的 ID,它可以作为一条日志唯一标识。节点中最大的 Log Index 也反映了这个节点的状态机是否与集群一致。
表格里的单元格表示日志项(Entry),其中的数字表示提交日志的任期(Term)。虚线框表示日志尚未提交,实线框表示日志已经提交。
我们可以点击 leader 节点的 request 按钮来查看向 Raft 集群提交数据的过程。
Leader 选举
Raft Scope 启动后会立即进行第一次 Leader 选举,在集群运行过程任何一个 Follower 出现心跳超时都会引发新一轮选举。
我们可以点击任意一个 Follower 的 time out 按钮模拟心跳超时,随后此 Follower 会发起新一轮选举。
或者我们可以点击 Leader 的 stop、restart 来模拟 Leader 宕机或者重启,并观察随后的集群选举过程。
比较奇怪的是, Raft Scope 中的 Leader 节点也可以通过点击 time out 来模拟心跳超时,在实际的 Raft 集群中 Leader 节点通常不会对自己进行心跳检测。
Leader 选举的更多介绍可以查看:Leader选举。不过 The Secret Lives of Data 有两处说的可能不太清楚:

这里的选举超时是指新一轮选举开始时,每个节点随机思考要不要竞选 Leader 的时间,这个时间一般100-到200ms,非常短。

Candidate 发起选举时会将自身任期(Term)+1并向其它所有节点发出 RequestVote 消息,这条消息中包含新任期和 Candidate 节点的最新 Log Index
收到 RequestVote 的节点会进行判断:
def onRequestVote(self, request_vote)
if request_vote.term <= self.term:
# 若 RequestVote 中的任期小于或等于(<=)当前任期
# 则继续 Follow 当前 Leader 并拒绝给 RequestVote投票
return False
if request_vote.log_index < self.log_index:
# 若 request_vote 发送者的 log_index 不如自己新,节点也会拒绝给发送者投票
# 这种机制确保了已经提交到集群中的日志不会丢失,即保证 Raft 算法的安全性
return False
if self.voted_for is None:
# 若在本 term 中当前节点还未投票,则给 request_vote 的发送者投票
self.voted_for = request_vote.sender
return True
else:
return False
Follower 超时
现在我们研究一下 The Secret Lives of Data 没有详细说明的 Follower 超时处理过程。
我们可以点击任意一个 Follower 的 time out 按钮模拟心跳超时,随后此 Follower 会发起新一轮选举。
根据上文中的 onRequestVote 逻辑,超时的 Follower 的 Log Index 是否与集群中的大多数节点相同决定了这次选举的不同结果。
首先来看超时 Follower 的 Log Index 与集群中大多数相同的情况:

现在我们点击 S5 的 time out 按钮,随后我们看到 S5 发起了一轮投票。因为 5 个节点的 Log Index 是一致的, 所以包含原 Leader 在内的大多数节点都投票给了 S5。

现在 S5 成为了新一任 Leader.

接下来我们看另外一种情况。S5 由于网络问题没有收到带有 Log Entry 1 的心跳包并导致心跳超时,S5 随后会发起一次投票:

由于 S5 的 Log Index 比较小其它节点拒绝投票给他,集群 Leader 和任期不变:

日志复制
日志复制的介绍您可以查看:日志复制
现在我们进一步探究日志复制的过程:
- 客户端将更改提交给 Leader, Leader 会在自己的日志中写入一条未提交的记录(Entry)
- 在下一次心跳时 Leader 会将更改发送给所有 Follower
- 一旦收到过半节点的确认 Leader 就会提交自己日志中的记录4
- 并向客户端返回写入成功
- Leader 会在下一次心跳时通知所有节点提交日志

这里比较复杂的情况是在第 4 步完成之后 Leader 崩溃。由于此时客户端已经收到了写入成功的回复,所以在选出新的 Leader 之后要继续完成提交。
在 Leader 提交了自己的日志后我们立即关掉 Leader:

随后集群发起了一次选举,S3 成为新任 Leader:

可能是因为 Raft Scope 存在 Bug, S3 本应该当选后立即完成提交工作。但是实际上需要我们再一次 Request 之后,日志1 和日志 2 才会被一起提交。

脑裂问题
在 Leader 崩溃时可能会有多个节点近乎同时发现心跳超时并转变为 Candidate 开始选举:

其它节点投票情况多种多样,但只要保证获只有得到过半投票的候选人才能成为 Leader。那么选举结果只有两种可能:
- 有且只有一个候选人获得过半投票成为 Leader 并开始新的任期
- 没有一个候选人获得过半投票,没有选出 Leader 进入下一轮投票
绝对不会选出多个 Leader

网络分区问题
Raft 甚至可以在网络分区的情况下正常工作:

在发生网络分区后可能存在 3 种情况:
任意分区中的节点数都不超过一半:这种情况只有集群被分成 3 个或更多分区时才会出现,十分罕见。因为 Leader 选举和 Commit Log 都需要超过一半节点确认才可以进行,在这种情况下 Raft 集群不能正常工作。
leader 所在的分区有超过一半的节点:这种情况视作其它分区中的 Follower 宕机,系统仍然可以继续工作。在分区修复后,Follower 节点会重新与 Leader 同步。
leader 所在分区中节点数不超过一半,但存在节点数超过一半的分区。这种情况最为复杂:

C、D、E 所在的分区节点数超过一半且与原来的 Leader 无法通信,随后 C、D、E 在心跳超时后会发起新一轮投票选出新的 Leader 并恢复工作。
原领导者 Node B 仍然会认为自己是集群的 Leader,但是由于只能与两个节点通信(包括自己)无法得到过半节点同意,所以无法完成日志提交。
在分区修复后 Node B 会收到 Node C 的心跳并发现对方的任期(Term)比自己高,Node B 会放弃 Leader 身份转为 Node C 的 Follower 与它保持同步。

总结
经过本文探讨我们可以总结一下 Raft 的一些特性:
- 只要集群中有超过一半的节点可以正常工作,集群就可以工作
- 只要写入成功的数据就不会再丢失
- 任意节点上保存的状态可能会落后于集群共识但是永远不会出现错误的提交。只要系统仍然在正常工作,节点上的状态一定会在某个时间后与系统共识达成同步,即保证最终一致性
- 只要在某个节点上读到了某个变更, 在此之后这个节点上永远可以读到该变更,即保证单调一致性
推荐阅读:
看动画轻松学会 Raft 算法的更多相关文章
- 【动画】看动画轻松理解「Trie树」
Trie树 Trie这个名字取自“retrieval”,检索,因为Trie可以只用一个前缀便可以在一部字典中找到想要的单词. 虽然发音与「Tree」一致,但为了将这种 字典树 与 普通二叉树 以示区别 ...
- 看图轻松理解数据结构与算法系列(NoSQL存储-LSM树) - 全文
<看图轻松理解数据结构和算法>,主要使用图片来描述常见的数据结构和算法,轻松阅读并理解掌握.本系列包括各种堆.各种队列.各种列表.各种树.各种图.各种排序等等几十篇的样子. 关于LSM树 ...
- 从分布式一致性到共识机制(二)Raft算法
春秋五霸说开 春秋五霸,是指东周春秋时期相继称霸主的五个诸侯,“霸”,意为霸主,即是诸侯之领袖.典型的比如齐桓公,晋文公,春秋时期诸侯国的称霸,与今天要讨论的Raft算法很像. 一.更加直观的Raft ...
- 一文搞懂Raft算法
raft是工程上使用较为广泛的强一致性.去中心化.高可用的分布式协议.在这里强调了是在工程上,因为在学术理论界,最耀眼的还是大名鼎鼎的Paxos.但Paxos是:少数真正理解的人觉得简单,尚未理解 ...
- 学习Raft算法的笔记
Raft是一种为了管理日志复制的一致性算法.它提供了和Paxos算法相同的功能和性能,但是它的算法结构和Paxos不同,使得Raft算法更加容易理解并且更容易构建实际的系统.为了提升可理解性,Raft ...
- Raft算法,从学习到忘记
Raft算法,从学习到忘记 --Raft算法阅读笔记. --Github 概述 说到分布式一致性算法,可能大多数人的第一反应是paxos算法.但是paxos算法一直以来都被认为是难以理解,难以实现.S ...
- 关于raft算法
列出一些比较好的学习资料, 可以经常翻一番,加深印象 0 raft官方git 1 raft算法动画演示 2 Raft 为什么是更易理解的分布式一致性算法 3 raft一致性算法 4 Raf ...
- 【转】分布式一致性算法:Raft 算法(Raft 论文翻译)
编者按:这篇文章来自简书的一个位博主Jeffbond,读了好几遍,翻译的质量比较高,原文链接:分布式一致性算法:Raft 算法(Raft 论文翻译),版权一切归原译者. 同时,第6部分的集群成员变更读 ...
- 分布式系统的Raft算法
好东西~~ 英文动画演示Raft 过去, Paxos一直是分布式协议的标准,但是Paxos难于理解,更难以实现,Google的分布式锁系统Chubby作为Paxos实现曾经遭遇到很多坑. 来自Stan ...
随机推荐
- Wormholes (spfa)
一种路是双向的,路的长度是正值:另一种路是单向的,路的长度是负值: 如果有负环输出YES:否则输出NO:不同的路可能有相同的起点和终点:必须用邻接表 While exploring his many ...
- [CF套题] CF-1163
CF-1163 传送门 # Penalty A B1 B2 C1 C2 D E F 3 (483) 464 +0 0:06 +1 01:13 +3 01:12 + 01:57 + 01:56 A 第一 ...
- Codeforces Round #676 (Div. 2) XORwice、Putting Bricks in the Wall、Palindromifier
题目链接:XORwice 题意:给你两个数a.b.求一个数x,使得((a异或x)+(b异或x))这个值最小,输出最小那个x 题解: 输出(a|b)-(a&b)就行(猜了一手 代码: #incl ...
- poj3585 Accumulation Degree(树形dp,换根)
题意: 给你一棵n个顶点的树,有n-1条边,每一条边有一个容量z,表示x点到y点最多能通过z容量的水. 你可以任意选择一个点,然后从这个点倒水,然后水会经过一些边流到叶节点从而流出.问你最多你能倒多少 ...
- vi、wc、gzip、bzip2、tar、yum安装、dpek、用户信息操作等命令
命令模式 输入"dd"即可将这一行删除 按下"p"即可粘贴 插入模式: a:从光标这个位置之后插入 A:在行尾插入 i:从光标之前插入 I:行首插入 o:在光标 ...
- 用了很多年Dubbo,连Dubbo线程池监控都不知道,觉得自己很厉害?
前言 micrometer中自带了很多其他框架的指标信息,可以很方便的通过prometheus进行采集和监控,常用的有JVM的信息,Http请求的信息,Tomcat线程的信息等. 对于一些比较活跃的框 ...
- bzoj4355 Play with sequence(吉司机线段树)题解
题意: 已知\(n\)个数字,进行以下操作: \(1.\)区间\([L,R]\) 赋值为\(x\) \(2.\)区间\([L,R]\) 赋值为\(max(a[i] + x, 0)\) \(3.\)区间 ...
- 全方位构造免杀 webshell 小结[一]
转载自https://klionsec.github.io/2017/10/11/bypasswaf-for-webshell/ 全方位构造免杀 webshell 小结[一] 前言: 本 ...
- 一分钟搞懂JavaScript中的JSON对象
JSON(JavaScript Object Notation)是表示值和对象的通用格式. JavaScript 提供了如下方法: JSON.stringify 将对象转换为 JSON. JSON.p ...
- 概率分析方法与推断统计(来自我写的python书)
在数据分析统计的场景里,常用的方法除了描述性统计方法外,还有推断统计方法,如果再从工作性质上来划分,推断统计包含了参数估计和假设验证这两方面的内容.而推断统计用到了很多概率统计方法,所以本小节在介绍推 ...