Raft其实是一种分布式一致性算法(分布式共识算法)。核心还是和Paxos差不多但是更加便于理解和实现,Raft算法模块化的拆分以及相比Paxos更加简化的设计。实现Raft协议更加的简单,理解Raft算法也更加的容易。主要拆分成了多个模块:
Leader selection 。选举出来新的Leader1.1 强Leader

1.2 复制状态机
一致性算法是从复制状态机的背景下提出的:
2.1Raft的三种角色以及角色相互变动

将上面的图转换一下:

服务器状态。跟随者只响应来自其他服务器的请求。如果跟随者接收不到消息,那么他就会变成候选人并发起一次选举。获得集群中大多数选票的候选人将成为领导人。在一个任期内,领导人一直都会是领导人,直到自己宕机了。

2.2 服务节点状态
所有服务节点的持久性状态 (在响应 RPC 请求之前,已经更新到了稳定的存储设备):
| 参数 | 解释 |
|---|---|
| currentTerm | 服务器已知最新的任期(在服务器首次启动时初始化为0,单调递增) |
| votedFor | 当前任期内收到选票的 candidateId,如果没有投给任何候选人 则为空 |
| log[] | 日志条目;每个条目包含了用于状态机的命令,以及领导人接收到该条目时的任期(初始索引为1) |
所有服务节点的易失性状态:
| 参数 | 解释 |
|---|---|
| commitIndex | 已知已提交的最高的日志条目的索引(初始值为0,单调递增) |
| lastApplied | 已经被应用到状态机的最高的日志条目的索引(初始值为0,单调递增) |
Leader上的易失性状态 (选举后已经重新初始化):
| 参数 | 解释 |
|---|---|
| nextIndex[] | 对于每一台服务器,发送到该服务器的下一个日志条目的索引(初始值为领导人最后的日志条目的索引+1) |
| matchIndex[] | 对于每一台服务器,已知的已经复制到该服务器的最高日志条目的索引(初始值为0,单调递增) |
2.3 Raft中三类RPC
RequestVote RPC(选举投票): 由Candidate发出的用于选举投票Leader的RPC请求
AppendEntries RPC(追加Log Entry): 由领导人调用,用于日志追加。同时也可以当做心跳使用。
InstallSnapshot RPC(安装快照): 安装快照的新的 RPC 来发送快照给太落后的跟随者,由Leader发出。


2.4 Raft算法特性总结
| 特性 | 解释 |
|---|---|
| 选举安全特性 | 对于一个给定的任期号,最多只会有一个领导人被选举出来 |
| 领导人只附加原则 | 领导人绝对不会删除或者覆盖自己的日志,只会增加 |
| 日志匹配原则 | 如果两个日志在某一相同索引位置日志条目的任期号相同,那么我们就认为这两个日志从头到该索引位置之间的内容完全一致 |
| 领导人完全特性 | 如果某个日志条目在某个任期号中已经被提交,那么这个条目必然出现在更大任期号的所有领导人中 |
| 状态机安全特性 | 如果某一服务器已将给定索引位置的日志条目应用至其状态机中,则其他任何服务器在该索引位置不会应用不同的日志条目 |

说明:Raft 在任何时候都保证以上的各个特性
2.5 安全性说明
选举限制
提交之前任期内的日志条目
一条已经被存储到大多数节点上的老日志条目,也依然有可能会被未来的领导人覆盖掉。Raft 永远不会通过计算副本数目的方式去提交一个之前任期内的日志条目。只有领导人当前任期里的日志条目通过计算副本数目可以被提交;

如图的时间序列展示了为什么领导人无法决定对老任期号的日志条目进行提交。在 (a) 中,S1 是领导人,部分的(跟随者)复制了索引位置 2 的日志条目。在 (b) 中,S1 崩溃了,然后 S5 在任期 3 里通过 S3、S4 和自己的选票赢得选举,然后从客户端接收了一条不一样的日志条目放在了索引 2 处。然后到 (c),S5 又崩溃了;S1 重新启动,选举成功,开始复制日志。在这时,来自任期 2 的那条日志已经被复制到了集群中的大多数机器上,但是还没有被提交。如果 S1 在 (d) 中又崩溃了,S5 可以重新被选举成功(通过来自 S2,S3 和 S4 的选票),然后覆盖了他们在索引 2 处的日志。反之,如果在崩溃之前,S1 把自己主导的新任期里产生的日志条目复制到了大多数机器上,就如 (e) 中那样,那么在后面任期里面这些新的日志条目就会被提交(因为 S5 就不可能选举成功)。 这样在同一时刻就同时保证了,之前的所有老的日志条目就会被提交。
3.1 领导人选举(Leader election)
触发时机: Raft服务集群启动、Follower没有定时收到Leader heartbeat、Candidate选举超时都会触发。也就是选举超时触发
随机选举超时时间: Raft 算法使用随机选举超时时间的方法来确保很少会发生选票瓜分的情况,就算发生也能很快的解决。为了阻止选票起初就被瓜分,选举超时时间是从一个固定的区间(例如 150-300 毫秒)随机选择。
选举流程:

选举的操作:
Leader选取原则:
安全性:
一个 term,最多选出一个 leader,可以没 leader,下一个 term 再选

当一个集群中的节点是偶数个的时候,就有可能在某一轮选举投票过程中不能选举出Leader,因为可能会出现两个节点获得的投票一样。导致重新开始选举。如果没有特殊方式限制,理论上存在每次都出现投票获得情况一样。而奇数节点就能大大减少这种情况。
影响选举成功的时参数
RTT << Heartbeat timeout < Election timeout(ET) << MTBF3.2 日志复制

Leader被选出后负责处理Client的请求, Append Log Entry请求只能通过Leader进行复制转发到Follower。

日志格式: logIndex+Term+log body ,条日志至少包含三个类型的的数据。logIndex+Term 确定一条日志。
Log Replication 特点:
连续性,日志不允许出现断层。都是自然数递增的情况,例如:1、2、3......n。
日志特性
Follower日志恢复
Follower从对比Leader的lastIndex来判断Follower是否需要从Leader复制Log填充,或者truncate Follower 多余的日志。
3.3 Committed Index
Committed Index(TermId, LogIndex):本质上Log Index一样。都是Log的索引但是指向位置不同。
Committed Index推进
3.3 日志压缩(Log Snapshot)
Raft 的日志在正常操作中不断地增长,但是在实际的系统中,日志不能无限制地增长。随着日志不断增长,他会占用越来越多的空间,花费越来越多的时间来重置。如果没有一定的机制去清除日志里积累的陈旧的信息,那么会带来可用性问题。
Snapshot 是最简单压缩方法。整个系统的状态都以快照的形式写入到稳定的持久化存储中,然后到那个时间点之前的日志全部丢弃。

Raft 中快照的基础思想。每个服务器独立地创建快照,只包括已经被提交的日志。主要的工作包括将状态机的状态写入到快照中。Raft 也包含一些少量的元数据到快照中:最后被包含索引指的是被快照取代的最后的条目在日志中的索引值(状态机最后应用的日志),最后被包含的任期指的是该条目的任期号。保留这些数据是为了支持快照后紧接着的第一个条目的附加日志请求时的一致性检查,因为这个条目需要前一日志条目的索引值和任期号。
快照中包含的元数据:
| 参数 | 解释 |
|---|---|
| lastIncludedIndex | 快照中包含的最后日志条目的索引值 |
| lastIncludedTerm | 快照中包含的最后日志条目的任期号 |
Install Snapshot(安装快照)RPC:
由Leader以将快照的分块发送给跟随者。领导人总是按顺序发送分块。
| 参数 | 解释 |
|---|---|
| term | 领导人的任期号 |
| leaderId | 领导人的 ID,以便于跟随者重定向请求 |
| lastIncludedIndex | 快照中包含的最后日志条目的索引值 |
| lastIncludedTerm | 快照中包含的最后日志条目的任期号 |
| offset | 分块在快照中的字节偏移量 |
| data[] | 从偏移量开始的快照分块的原始字节 |
| done | 如果这是最后一个分块则为 true |
| 结果 | 解释 |
|---|---|
| term | 当前任期号(currentTerm),便于领导人更新自己 |
接收者实现:
term < currentTerm就立即回复首先任何集群中的Raft服务直接从旧的配置直接转换到新的配置的方案都是不安全的,不可能做到原子转换所有的集群服务。所以在转换期间整个集群存在划分成两个独立的大多数群体的可能性,需要在保证 安全性 的前提下完成:不能在同一 term 有多个 leader,否则可能存在 term 和 index 相同但内容不同的 log entry。

集群服务从 3变成了 5 。不幸的是,存在这样的一个时间点,两个不同的领导人在同一个任期里都可以被选举成功。一个是通过旧的配置,一个通过新的配置。
Raft给出的解决方案是:Raft集群先切换到一个过度的配置也叫共同一致(joint consensus),共同一致是新老配置的结合。一旦共同一致已经被提交,那么系统就切换新的配置。
好处:不影响安全的情况下,集群配置转换的过程依然可以响应客户端请求。
