Raft论文学习 - 基础算法

Posted 不油腻的儿子娃娃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Raft论文学习 - 基础算法相关的知识,希望对你有一定的参考价值。

共识Consensus

Raft是一种共识算法。那么什么是共识呢?

共识指的是存在有很多的server,如何对==共享的状态能达成一致==,即使在存在有server挂掉的情况下也能达成一致

consensus对于consistent large-scale storage system来说是非常重要的

复制状态机RSM

复制状态机(Replicated State Machine,简称RSM)指的是由N台(一般情况下N = 3或者5)连接起来并提供相同状态的一组Server列表,通过RSM可以使得Server之间达成共识Consensus。

复制状态机使用==Replicated Log==来实现,在Replicated Log里存储了一系列、有序的Command,然后State Machine按照Log里的Command的顺序进行执行。由于初始的State Machine是确定的,各个Server获取得到的Replicated Log里的Command顺序是确定的,因此更新完各个Server上得到的State Machine的状态是确定的。

如上面的描述,必须要确保Replicated Log是一致的,共识算法就是为了解决这个问题的。

Keeping the replicated log consistent is the job of consensus algorithm.

针对上图的内容,包括有这几个步骤:

  1. 由Consensus Module接收来自Client的Command
  2. 将从Client接收到Command数据,写入到本地的Log中;并同其他Server的Consensus Module交互,确保其他Server的Log中按照相同的顺序写入Command数据。一旦Command数据被充分地复制,就认为该Command数据被提交(committed)了。
  3. 每个Server的State Machine按照Log中的顺序挨个地处理已经提交的Command数据
  4. 返回给Client输出结果

Raft算法基础

Raft是一种管理Replicated Log的算法。用一句话来介绍Raft如何来管理Replicated Log:

  1. 先选出来Leader
  2. 由Leader来管理Replicated Log。 当然好奇的会问,Leader如何来管理Replicated Log?如上面的RSM部分所介绍的,
    • 接收来自Client的Command数据(log entries)
    • 将数据复制到其他的其他的Server
    • 通知Server可以安全将log entries数据应用(Apply)到State Machine里

整体概览

状态State

从角色的角度来看,分为Follower, Candidate, Leader三种角色,在RSM集群中的各个Server的角色在这三个角色之间来回转换。

任期Term

除了角色外,每次选主都会带来Term的增加。TermEpoch类似,每个Term相当于一个朝代,在每个朝代更迭的时候,一上来需要重新进行选主,即Leader Election的过程

Term就是一个逻辑时钟的概念,使用Term来区分哪些是过期的Leader。在整个RSM集群中,任何一方发现自己的Term落后了,那么就必须要变成Follower的状态;任何一方收到来自过期的Term的请求,都采取直接拒绝掉的方式处理。

通信RPC

在Raft基础算法中,只有两类的RPC请求

  • RequestVote RPC,选主阶段中由Candidate来发送请求
  • AppendEntries RPC, 在日志复制阶段中,由Leader发起将log entries复制到RSM集群的其他Server上去,同时也用来维护各个Server之间的心跳

在Raft中,都是基于RPC的方式来进行通信,因此在每次RPC中,都必须有Request和Response,若在指定时间内没有收到Response,那么是发起方有义务进行重试操作;为了优化性能,RPC之间会并行地发起,因此不能假设网络能保留RPC请求之间的顺序。

Raft算法基础中,可以分为如下三个相对独立的子问题:

  1. Leader Election选主,初始化一个新的集群或者Leader挂掉了之后再重新进行选主
  2. Log Replication日志复制,包括接收来自Client的log entries,并复制到各个集群里去
  3. Safety安全性,确保State Machine Safety Property: ==如果任何一台Server将一个log entry应用到State Machine里去,那么一定不存在其他的Server在相同的log index上应用了不同的Command数据==。

Leader Election选主

若存在Leader,则使用心跳机制(使用没有任何log entries的AppendEntries的RPC操作)来确保Leader是存活的。若Follower在election timeout时间内没有收到任何的来自通信,就会认为当前没有可用的Leader并发起选主。

选主的过程:

  1. 将自己的currentTerm做加1操作,并将自己的状态变成Candidate
  2. 给自己投票,同时并行地往集群里其他的Server发起RequestVote RPC请求
  3. 然后持续作为Candidate状态直到如下三个事件中的任何一个发生:
    • 自己赢得了选举(自己赢)
    • 其他的Server变成了Leader(别人赢)
    • 选举超时到了,但是还没有任何一个Server赢得了选举(没人赢)

上面的选举超时时间(Election Timeout)是很有讲究的,若设置不好,很有可能都陷于上面的第三种情况(==split vote==),一直产生不了获取多数票的Leader,因此为了规避,采取在一个区间[150ms ~ 300ms]里随机选择一个值作为选举超时时间。

Log Replication日志复制

Log Entries 日志条目

在日志复制模块里,首先明确复制的是什么内容,即本小结里需要阐述的log entries的内容。

从上图可以得到如下的信息:

  1. log是由编号的entries组成,该编号叫做log index,用来表示该entry在整个log的位置
  2. 在每个log entry里,包括有2部分内容,第一部分是term信息,第二部分是command数据,该command数据需要用来交互并提交到State Machine里去
  3. committed的概念,任何一个log entry可以安全地应用到State Machine里去了的话,就认为是可以committed。

A log entry is committed once the leader that created the entry has replicated it on a majority of servers

Raft guarantees that committed entries are durable and will eventually be executed by all of the available state machines

若一条log entry被提交(Committed)了,那么该log entry之前的log entry都会提交。然后在Leader里会记录当前已经提交的最大的log index,并在AppendEntries RPC里包括有这个最大的log index信息(==状态里叫做commitIndex,RPC里叫做leaderCommit==),其他的Follower收到这个信息,可以将小于或等于最大的log index的entry数据都应用到自己本地的状态机里去。

Log Matching Property

非常重要的两条属性

  1. If two entries in different logs have the same index and term, then they store the same command
  2. If two entries in different logs have the same index and term, then the logs are identical in all preceding entries

上面的描述中存在有==相同的index和term==的限制,只有满足这个限制,当前的log entry是一样的,以及之前的所有的log entries也是相同的。

可以通过如下的两个来保证上面的两条属性:

  1. leader一旦创建了log entry,就不能挪动其位置
  2. 每次leader在发送AppendEntries的时候,除了带上新创建的log entry外,还带上前面一个log entry的log index、term信息(==prevLogIndex, prevLogTerm==)。若Follower接收到AppendEntries之后,发现有冲突则拒绝掉

异常处理

对于日志复制过程中,若正常情况,都能满足Log Matching Property需求。但是若leader或者follower宕机的情况下就比较复杂了,因此需要进行异常处理。

上面这张图的解读如下,若leader的状态如最上面的log所示,那么各个follower会出现的状况有:

  1. (a)(b)的情况,即follower会丢失一些log entries.
  2. (c)(d)的情况,即follower会多一些uncommitted log entries
  3. (e)(f)的情况,即follower比leader又多一些log entries,同时又少一些log entries

ps. 很复杂的情况即如上面的(f)的状况,在term 2的时候作为leader,然后存在有几个log entries发出去但是没有收到commit就crash了,然后再立马恢复起来,作为term 3的leader,又发出来几个log entries但还是没有commit掉就又crash掉了,然后再就一直处于crash的状态下。

发现了leader和follower在日志上存在有不一致的情况了,那么该怎么做呢?

总体指导思想: ==In Raft, the leader handles inconsistencies by forcing the followers’ logs to duplicate its own==

具体操作步骤:

  1. 找到leader和各个follower在log上一致的log index信息,即从这个log index之后的数据都是非一致的
  2. 将leader中的log index之后的log entries数据都发送给follower。

在处理冲突的情况下,Leader是不变的,即Leader角色的log是immutate的,Follower的log是mutate状态,跟Leader保持一致

Safety安全性

回到最根本的问题,Raft是共识协议,即各个Server就共享状态能达成一致,在各个Server上的共享状态是一致的,也就是说在State Machine里的数据是一致的。

那么通过选主和日志复制,能达成上面的目标吗?还需要再增加一些约束才能。具体约束条件为: Leader Completeness Property,即通过选举出来的Leader必须要存在有之前所有的已经提交的Log Entries.

选举约束

在任何的leader-based共识算法,都必须要确保leader是包括了所有的已经提交的log entries。虽然有一些共识算法(如Viewstamped Replication),没有要求必须要包括所有的,但是后面可以从其他的Server来获取到log entries来补充到leader里来,但是这样做法使得算法搞的比较复杂。而Raft简化了下,增加约束,Leader必须要包括有所有的已经提交的log entries。这样对于AppendEntries RPC操作,只是单向的,即从Leader到Follower,Leader的log是不变的。

如何处理上一个Term没有提交的log entries

比较复杂的情况,即使复制到了一个Log entry存在于大多数的Server上去了,也不能看成提交的(Commit)。即如上图中的index = 2, term = 2的log entry,该怎么处理呢?取决于后面的leader是S1还是S5。若是S5是Leader,还是可以将虽然复制到多数Server的index = 2, term = 2的log entry删除掉,即出现上面的(d1)的情况。

为了避免上面的状况,Raft不能只是简单地通过数数的方式来决定是否提交之前的Term的log entry。而是通过决定Leader的Term的log entries是否复制到大多数来决定之前的Term的log entries的提交,即在(d1)和(d2)里都是来决定是否要将index = 2, term =2 的log entry保留下来还是消除掉。

安全性证明

可以通过Log Completeness Property,可以得到State Machine Safety Property。

Follower和Candidate宕机情况

上面的很大部分处理的是Leader宕机,然后使用Raft算法进行选主,选主出来之后再进行日志复制进行工作。在实际中,会出现Follower或者Candidate宕机的情况,或者Leader和Follower/Candidate之间的网络连接断开的情况。这样各个RPC会怎么样呢?

==Raft会一直进行重试,直到完成所有的RPC操作==, 即发送Request失败的话,会一直发送;发送Request成功但是没有收到Response,也会一直发送Request;因此Follower有责任来判断需要AppendEntries的数据是否是之前的RPC里已经Append成功了,若成功了,那么则需要忽略掉

持久化状态和Server重启

为了避免Server重启使得一些状态丢失,需要将一些状态持续化掉。如

1. currentTerm, Server看过的最新的term, 在刚启动的时候设置为0,后面单调递增
2. votedFor, 当前term下收到的vote中的candidateId,若没有则为null,避免在同一个term下投票大于0次
3. log[], log entries

除了需要持久化的状态,有一些状态是不需要持久化的,如commitIndex

State Machine部分的内容,可以是非持久化的,也可以是持久化的。若State Machine是非持久化的,那么从log entries进行重新应用就可以了,或者持久化的数据再从last applied index开始重新应用也可以得到全部的State Machine内容。

Timing和可用性

Raft协议里,对于安全性方面的要求是不能依赖Timing,但是可用性(系统在指定的时间区间里给Client有应答)是依赖Timing。对于Timing上,存在有如下的要求:

典型情况下, 的时间约为0.5-20ms, 的时间约为10-500ms, 一般是几个月级别的

以上是关于Raft论文学习 - 基础算法的主要内容,如果未能解决你的问题,请参考以下文章

Raft算法论文(部分)

Raft论文

区块基础-RAFT

raft论文阅读

Raft算法国际论文全翻译

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