【学习笔记】分布式一致性算法——Raft算法

写在前面

花了好长时间终于吧这篇笔记肝出来了QAQ,最近确实好久没做笔记了(虽然这篇笔记是为毕设服务的),后面估计会多发几篇笔记,鞭策下自己吧QwQ。

什么是Raft

Raft是更易理解的共识算法,相比于Paxos来说,Raft通过两个主要操作来使得问题更佳简化:

  1. 状态简化:将角色进行简化,减少状态数量和可能发生的变动。
  2. 将问题分解为三个子问题:Leader选举、日志复制、安全性。

Raft的易理解性,论文中也有调查分析证明。通过对学习Paxos和学习Raft的学生考试对比,学习Raft的考试得分普遍高于Paxos的考试得分。

前置知识——复制状态机

相同的初始状态状态A + 相同的一系列指令 = 相同的结束状态状态B

指所有节点在相同的状态A下,执行一系列指令后,都能得到一致的状态B。这保证了分布式节点的一致性。

前置知识——角色(状态机)

  1. Leader 领导节点
  2. Candicate 候选人
  3. Follower 跟随者(每个节点启动时的初始状态)

前置知识——RPC

节点之间使用远程过程调用(RPC)进行,通信。Raft的RPC有三种:

  1. RequestVote RPC: 候选人(candidate)发起选举请求
  2. AppendEntries RPC:表示leader复制日志到follower,也表示心跳包
  3. InstallSnapshot RPC:对于日志落后较大的follower,leader使用该RPC发送快照给follower,避免了大量AppendEntries RPC请求。

子问题1 —— Leader选举

任期(term)

  • 首先每个节点都有一段时间周期term(任期),term(任期)是递增的,且每个term(任期)中的时间是随机的(论文中给出的时间是150ms-300ms)。
  • 选举过程中,follower当收到不是最新的任期(不是大于自己的任期)的rpc选举请求将会拒绝。假如是最新任期,follower则会把自身currentterm(当前任期)设置为最新的任期。

心跳机制

假设当前节点中有leader,leader会不断向其他节点发送心跳包(一种特殊的AppendEntries RPC),当flower节点收到这个RPC时,会重置该任期的时间,这样就不会出现任期内时间周期超时的情况。

选举机制

【学习笔记】分布式一致性算法——Raft算法_第1张图片

假设一开始没有Leader,这时候当有follower当前term的时间周期结束时就会自动转变为candicate,并向集群中其他节点发起选举请求。

【学习笔记】分布式一致性算法——Raft算法_第2张图片

选举过程如下:

  1. (1)follower当前term时间周期结束时,term值+1,发起选举并投自己一票。 (2)candidate需要得到超过一半节点的选票才能当选leader,并通知当选声明。如果节点在candidate状态时收到Leader节点的心跳后,如果新Leader的任期不小于自身任期,则将自身状态由candidate转变为follower。 (3)如果没有candidate获得超过半数选票,candidate节点将在自身任期超时后将自身任期+1,继续步骤1.2。
  2. 每个节点保存了自己term的值,如果candidate在选举过程中发现自己的term不是最大的,就会立即变更为follower角色,如果follower收到了term比自己小的节点的选举请求,会直接拒绝请求。这个机制可以保证集群的数据基本是最新的。
  3. 有节点当选leader后,会定期给其他节点发送心跳包,其他节点收到leader的心跳包就会立刻重置自己term内的时间周期。当leader挂了或者出现网络分区,将回到步骤1。

子问题2 —— 日志复制

当节点当选leader时,对于客户端发送的每个命令,leader都会储存在对于的日志条目(log entity)中,对于日志条目基本信息如下:

  • 索引号(index)
  • leader任期号(term)
  • 执行的指令

【学习笔记】分布式一致性算法——Raft算法_第3张图片

当客户端请求执行指令,客户端就会发送到Raft集群的其中一个节点中,如果发送到follower节点,就会将这个指令转发到leader节点中。

如果发送到leader节点,leader节点会生成一条日志(log entity),并将这条日志通过AppendEntries RPC向follower节点追加日志。

需要注意的是,此时日志并没有真正提交,follower节点收到leader发来的AppendEntries RPC请求后,复制该日志完成后向leader节点发送响应回复RPC,当有半数节点回复时,此时leader节点就可以将这条日志进行提交。

未回复的follower节点有两种情况:

  1. follower宕机或者回复较慢:leader节点会继续发送AppendEntries RPC,即使leader已经响应客户端
  2. follower从宕机状态回复,且日志不是最新的:此时会进行一致性检查操作。
  • 一致性检查操作:leader会不断发送AppendEntries RPC给flower,AppendEntries RPC中会包含上一条日志的索引号和任期号,follower判断本节点中是否存在索引号和任期号相同的上一条日志,如果存在则回复成功,不存在则回复失败。如果失败,leader会一直将发送索引号-1的追加日志条目RPC请求,直到follower回复成功为止,这样就可以定位到follower节点第一个缺失的日志。

此外,可能还存在leader节点宕机的情况。如果leader宕机一段时间,导致follower节点任期超时,就会触发leader选举流程。

这时选举出的新leader会出现和follower日志不一致的情况,这时会通过follower强制复制leader日志的手段解决问题。

子问题3 —— 安全性

为了处理边界问题,Raft论文中独立开辟了第三个子问题——安全性,该子问题主要分为四个部分:

  1. leader宕机 —— 选举限制
  2. leader宕机 —— 提交相关问题
  3. follower或candidate宕机
  4. 时间与可用性

leader宕机 —— 选举限制

举个例子

我们来看下面这张图:

【学习笔记】分布式一致性算法——Raft算法_第4张图片

这张图种我们不难看出,follower节点中有很多节点不具备完整的日志,是因为日志复制过程中只要日志复制到了大多数节点就可以提交。

假如这一时刻leader突然宕机了,日志缺失的节点当选leader,是十分危险的。所以要进行选举限制。

选举限制规则

如果candidate上的最后一个日志相比较与follower上的最后一个日志不是最新的(即使follower上最后一个日志是未提交的),那么follower将拒绝该candidate的选举请求。

这个新是这么定义的:

  1. 任期号不同,则任期号大的日志更新
  2. 任期号相同,则索引号大的日志更新

有了这样的规则,就能保证选举出来的leader日志是最新的。

leader宕机 —— 提交相关问题

再举个例子

【学习笔记】分布式一致性算法——Raft算法_第5张图片

我们分析一下上图:

  1. (a)时刻S1为leader,生成日志2,并复制到S2。
  2. (b)时刻S1宕机,S5获得S3、S4选票当选leader,任期号为3,生成日志3后宕机。
  3. (c)时刻S1故障恢复,重新当选leader,任期号为3时选举失败(因为任期号不是最新的,收到其他follower反对),任期号4时选举成功。S1日志2复制到了S3中(此时日志2未提交),生成日志4后又宕机了。

这里有个问题:(c)时刻中日志2可以提交吗?

假设日志2可以提交,那么(d)时刻,S5再次当选leader,然后把日志3覆盖复制到了所有节点中。这样日志2就被覆盖掉了,是十分危险的

日志提交规则

leader只能提交自己任期内的日志,不会提交非自身任期内的日志。

(但非自身任期内的日志时可以复制的(因为复制是安全的),且提交自身任期内日志时可以顺便把非自身任期内日志提交了,这个个人理解是因为日志时有序的,当新日志提交了,没提交的老日志就默认提交了,这个情况是安全的,不会导致数据不一致的情况。)

还是上面的例子

所以说,(d)时刻的情况是不存在的,即使S5再次当选leader,也要生成一个任期号为5的日志并提交,才能把别的节点未提交的日志覆盖了,否则(d)时刻这种情况也不一定是安全的。

(e)时刻的情况是合法的,S1将日志4复制到了大多数节点,此时已经构成大多数,S1可以把日志4提交了,这样日志2也就一并提交了。

follower或candidate宕机

这种情况是比较简单的,follower或candidate宕机的情况会导致rpc包未被接收。这是只用重复发送rpc包,直到对方节点响应位置。(rpc请求都是幂等的)

时间与可用性

Raft系统整体不受时间影响。也就是说rpc包接收顺序并不会影响日志顺序。

当设置选举超时时间时,主要要考虑两个因素:广播时间(个人理解为延时时间)和平均故障时间。

  • 当选举超时时间小于广播时间时,会导致follower节点无法接收到心跳包,导致无限选举的情况。
  • 当选举超时时间大于平均故障时间时,会导致还没选举出leader就宕机,进而导致选举不出leader的情况。

然而,一般平均故障时间都会比较长,所以不用太考虑这个因素影响,主要考虑广播时间。

总结来说就是:

广播时间 << 选举超时时间 << 平均故障时间

集群成员变更

分布式集群成员变更过程中,可能会发生leader中途宕机的,造成新旧配置选举出两个leader的情况。这种情况就是分布式系统中的脑裂问题。如下图所示。

【学习笔记】分布式一致性算法——Raft算法_第6张图片

图中为三节点扩容至五节点的某一时刻,绿色为旧配置节点,蓝色为新配置节点。此时leader突然宕机恢复。其中旧配置中集群数为3,还在旧配置的节点为1和2,两票可以在旧配置中当选出一个leader。新配置节点数为5,且切换到新配置的节点为3/4/5,三票可以在新配置中当选出一个leader。就会产生两个leader当选的情况。

Raft为了防止这一情况发生,在旧配置和新配置切换过程中加入一个过渡的配置——联合一致(C oldnew)。

联合一致状态下,投票规则如下:

在联合一致状态的节点,投票必须要通过新配置和旧配置的大多数节点才能成功。

这样就能避免脑裂问题了。

举个例子

【学习笔记】分布式一致性算法——Raft算法_第7张图片

还是这张图,但这次不是从旧配置直接切换到新配置,而是进入联合一致状态。那么这个时刻leader宕机恢复。重新开始选举。

节点12之间可以产生一个leader,我们假设节点1当选leader。回到节点3/4/5中,假设节点3发起选举,在新配置中,节点3可以拿到节点4/5和自己三票,构成新配置投票的大多数。但是在旧配置中,节点3拿不到节点1/2的选票,因为节点1当选了leader,所以节点三选举失败。通过联合一致状态,就解决了脑裂问题。

新增节点需要考虑的情况

新增节点时,因为新节点日志相比leader落后很多。此时可以先将新节点设置为只读(没有投票权,不参与日志计数),然后再复制节点,等到新节点日志追上leader后便可发起集群成员变更。

缩减节点需要考虑的情况

  1. 当集群成员缩减时,因为leader可能就是要缩减的节点,leader要在新配置配置完成后自动退位。此时他可以发送Cnew日志,但是日志计数是不计自身

  2. 为了避免下线的节点超时发起选举,影响集群运行。因为leader不会继续发送心跳包给下线的节点,造成要下线的节点任期超时发起选举。

    解决方法:服务器会在确信集群中有leader的情况下忽略选举请求。

    具体方式:如果节点在最小选举超时时间内收到RequestVote RPC是,不会对选举请求进行投票,即使发起选举的节点任期号更大,也不会根据选举请求中的任期号更改自己的任期号。这样节点必须等待一个最小选举参与时间才能处理选举请求,leader可以通过不断发送心跳包来维持集群正常运作,不会被即将下线的节点发起的选举请求干扰集群。

最后的话

本篇笔记是根据参考文献里的内容,以及Raft论文进行的个人对Raft相关内容的一篇知识梳理,知识量较大。虽然本人对相关知识进行了查阅论文考证,但是初学Raft算法,个人理解能力有限,如果有不正确的地方还望指出,感谢~

参考文献

知乎 —— raft共识算法 - Leader选举

csdn —— raft算法详解

csdn —— 分布式一致性协议:Raft算法详解

bilibili —— 解读共识算法Raft

github —— 寻找一种易于理解的一致性算法(扩展版) —— 中文翻译

你可能感兴趣的:(高并发服务器,学习,分布式)