Skip to main content

raft

📅 2026-02-05 ✏️ 2026-04-16 CS

1 · raft#

https://raft.github.io

https://raft.github.io/raft.pdf

https://thesecretlivesofdata.com/raft

问题拆分:

  1. leader election
  2. log replication
  3. safety and membership changes

reducing the number of states to consider 减少需要考虑的状态,减少不确定性

1.1 · 总览

选举出领导节点:客户端与领导节点交互,领导管理复制日志(复制到其他节点);

节点状态:leader, follower, candidate;正常情况:

  1. 只有一个leader,其余都是follower
  2. leader处理所有来自客户端的请求
  3. follower不会进行请求,只会响应来自leader/follower的请求
  4. candidate状态,是用来选举leader

term表示逻辑时间: 每一个term以一次选举开始,一个或多个的candidata会尝试成为leader, 如果一个candidate成功选举为leader,那么其负责这一term的剩余时间; 在某些情况下,选举时票数分裂,以无领导产生为结果,新的term会产生,表示开始新一轮选举; term 用作逻辑锁:持续递增,同步高term值的日志,Candidata/Leader回退Follower状态,低term值的Leader表示数据过期

状态变化:

flowchart
  Follower --1.timeout, start election--> Candidata
  Candidata --2.receive majority votes--> Leader

  Leader --3.discover higher term server --> Follower
  Candidata --4.discover current Leader or new term--> Follower

RPC调用:RequestVote, Append-Entries(复制日志、心跳), 还有一个快照的RPC?

1.2 · 1.选举领导#

心跳机制触发领导选举

跟随者只要收到领导或候选者的RPC,那么就一直是跟随者状态

  • 领导会阶段性的发送心跳给跟随者
  • 如果一个跟随者在一段时间内没有收到领导发送的心跳,那么就会开启选举

跟随者会增加自身的term,进入候选者状态, 候选者进行投票的RPC调用其他集群内的节点,

在这些情况下停止选举:1)获胜选举 2)其他节点是领导 3)没有获胜者,重新开始 1.候选者会在当前term赢得集群内大多数节点的投票而选举获胜; 2.等待投票期间,如果高term的领导节点发送心跳给候选者节点,候选者回退成跟随者状态,如果低于,则忽略 3.多个候选者且票数一致,则开启新一轮选举!!!!这种情况可能一直发生

解决问题3,跟随者进入候选者的超时时间是随机的,大概率保证只有一个候选者;候选者从新开始选举的超时时间也是随机的;

1.3 · 2.日志复制#

用户客户端向领导节点请求,领导节点记录请求执行的命令并追加到日志, 然后发送追加日志项RPC给集群内其他节点进行复制, 当领导节点认为安全的时候(比如大部份节点成功复制日志后), 领导节点会应用这个命令,并响应命令结果到客户端, 如果其他节点失败了,领导节点会一直重试RPC请求集群内其他节点

日志项含命令,含term值(用于判断一致性),index位置

领导节点请求的追加项的RPC中,会含有index,当跟随者知道有log项被应用了,其也会应用在本地

日志属性:不同日志相同索引的值相同;如果索引N的值相同,那么N以前的值都相同

跟随者节点会在收到追加项(包含之前的索引)的RPC的时,检查索引;如果不符合,会拒绝

领导节点出问题的时候,可能导致日志不一致; 当日志不一致的时候,领导节点会强制跟随者和其一致:找到日志相同的最晚的哪个索引处,删除跟随者后续的日志项,复制领导者节点的; 领导节点维护了跟随者节点的nextIDX表,发送RPC后,如果跟随者节点拒绝了,则减1,在发送RPC,循环往复,直到匹配,此时日志就一致了; 跟随节点可以在拒绝的时候,返回冲突的第一个索引;

领导节点不会重写其日志;

正常来说,一个日志项会在一次RPC调用时间内完成,慢节点不会影响性能。

1.4 · 3.安全性#

一个跟随者节点可能变得不可用,此时领导节点已经提交了多个日志项; 然后这个跟随者节点可能选举成功,变成领导节点,就会覆写上诉的日志项;

Leader Completeness Property 领导者完整性属性 限制:如果一个节点成为了领导节点,必须包含其term前所有term的日志提交;

1.4.1 · 1. 选举限制#

从新领导节点当选的那一刻起,所有来自前几任任期的已提交条目都会出现在新领导节点。 保证:日志项的数据流只会从领导节点到跟随者节点。

在投票阶段,如果一个候选者节点没有先前提交的所有日志项,那会被阻止成为赢得选举。 请求投票RPC会包含候选者节点的日志信息,投票者会根据其本身日志,和候选者日志进行对比后,再进行投票。

1.4.2 · 2. 来自前一个term的提交日志#

领导节点当前term的日志被提交,需要集群内大多数节点已经保存, 如果在日志提交之前,领导节点崩溃了,未来的领导会尝试完成这个日志的复制;

即使大多数节点被保存,也可能被未来领导节点覆写:Figure 8 只有当前领导者的日志可以被提交。

1.4.3 · 3. 安全性保证 5.4.3#

假设:term N 的提交,没有被 term N+n 的领导节点保存: 矛盾1: 矛盾2: 验证了假设的不可行

结论: 只要有一个节点应用了一个日志,那么其他节点一定一致;

1.5 · 4. 跟随节点、候选者节点崩了#

重试AE或RV

Raft 的 RPC 是幂等的

1.6 · 5. 时间和可用性#

安全性与时间无关;但可用性无可避免的依赖时间;

时间在Raft的领导选举中很重要:

广播时间(RPC调用) << 选举timeout << 节点失败时间

1.7 · 6. 集群成员变更#

TODO

1.8 · 7. 日志压缩#

TODO

1.9 · 实现

选举领导

  1. 节点之间通过RPC调用,节点本身是一个RPC服务
  2. 每个节点一启动就是跟随者节点状态
  3. 心跳时间超时,那么跟随者增加自身term,修改自身状态为候选者,先给自己投票,再向集群内其他节点请求RPC进行投票
  4. 候选者处理RPC投票响应:收到多数投票后立即成为领导者节点;如果选举超时仍未获多数票,则重新发起投票(新任期)

同步日志

日志复制和提交是两阶段分离的:AE请求中的leaderCommit是当前已确认多数节点写入的最高index,携带的日志条目可能尚未被多数确认,提交会在后续的AE/心跳中通知跟随者。 从客户端角度看只需一轮AE网络往返:多数节点(含Leader自身)确认写入后,Leader即可提交并回复客户端,不需要等所有跟随者。跟随者的apply是异步的。

  1. 用户(客户端)请求领导节点
  2. 领导者节点将未提交的数据追加到自己日志,并向集群内其他节点请求RPC追加日志(此时AE中的leaderCommit可能小于复制日志的index)
  3. 跟随者节点处理RPC请求,写入日志
  4. 领导者节点收到集群内多数节点确认后,推进commitIndex,应用日志到状态机,响应用户(客户端)
  5. 领导者节点在后续的心跳/追加日志RPC中携带更新后的leaderCommit跟随者节点据此推进自身commitIndex并应用已提交的日志