基础版raft算法

----暴躁老哥对raft的粗略理解

强烈建议大家先移步 http://thesecretlivesofdata.com/raft/#home ,花5分钟左右时间简单了解一下raft。

本篇文章主要谈一谈我对raft的一些理解,如有异议,欢迎各路大佬指出~

(下面讨论的是论文版Raft,各种改进版在此不做讨论)

1 Raft都干了什么?

1.0 raft解决了什么问题

相信大家在接触新事物之前都会有此类疑问,一堆为什么要问。不过【raft解决了什么问题】这个问题可以去问问谷老师或者某度哈哈哈。

1.1 概述

raft基于paxos,是一种用来管理复制日志(replicated log)的共识算法,所以也可以理解为,raft的各种设计都是为了让集群中的server在log上达成共识。

raft的作者认为paxos有两个“缺点”:1)paxos非常难理解。2)paxos工业上不好实现。如果非要说难理解是缺点的话倒也算是吧,毕竟大道至简。

一个raft集群有多台server(废话,一台server也不需要共识),给定某个时间点,每个server的状态能且只能为下面三种状态之一:

  • leader

    • leader诞生制度为选举制 (个人感觉raft准确来说是毛遂自荐+拉票)

    • 一个集群有且仅有一个leader 所有人都听它的

  • follower

    • 集群中机器初始化状态为follower

    • 每个follower都有个选举超时时间(election timeout),可先简单理解为follower在这个时间内没收到leader发来的心跳,则认为集群当前无leader,可以开始竞选leader了

  • candidate

    • 每个想要成为leader的follower,都会先从follower变为candidate状态

    • candidate先给自己投一票,如果能够获得集群中大多数投票则成功成为leader

raft 中时间被分为多个 term

集群中一个server的状态机如上图。简单起见,后面说的leader大多都是指【状态为leader的server】,follower/candidate同理。

正常情况下,集群只会有一个leader,多个follower/candidate。leader负责处理所有client的请求,而follower/candidate只和leader通信。

一个集群中能否有两个leader?是否可行?两个leader相比一个leader又有什么好处/坏处?

历史的车轮滚滚向前,朝代更替,永不停息。raft的世界亦如是,每个leader的诞生都伴随着一次或多次竞选(election),每一次竞选都会有一个或者多个follower变为candidate参加竞选,而每一次竞选的开始都意味着一个term的开始,每个term对应一个term number(此term number只增不减),因此,term number可称之为raft集群中的逻辑时钟(logical clock)。(为什么要专门搞个term number呢?用时间值不是更准确?万一有人有类似疑问,可以学习 分布式时钟 相关知识)

正如前面说的,一个term开始于一次竞选,而term以何种方式结束取决于竞选结果。如果竞选中没有一个candidate获得大多数的投票,则认为大家都竞选失败,此次term结束,开始下一轮竞选,如上图中t3。如果有candidate成功成为leader,则在该leader不再是leader之前称之为一个term,如上图term1 term2 term4...

集群中server之间通过RPC通信,基本的共识算法需要两种类型的RPC:

1)RequestVote RPC. 该类型RPC由candidate在竞选的过程中生成。

2)AppendEntries RPC. 该类型RPC由leader生成,主要用于日志同步和心跳。

为什么要这两种分开呢?一种RPC可以吗?

接下来从日志复制、领导者选举、成员变更三个方面进行介绍。

1.2 领导者选举 Leader Election

raft选主为多数原则,即超过半数的server赞成,则选主流程结束,此过程保证只有一个server赢得竞选。为了保证只有一个candidate竞选成功,在集群中节点投票时,每个节点按照first-come-first-served的原则,只能投出一票

集群会发生选举的情况大致分为以下几类:

  • 集群为初始态,此时所有server均为follower,需要选主。

  • leader无法发出心跳,需要选主。

  • follower未收到leader发出的心跳,需要选主。

每个follower都有一个选举超时时间(election timeout),在一个follwer选举超时时间内如果没有收到来自leader/candidate的有效RPC,则该follower认为此时集群没有leader,状态变为candidate,发起一场竞选。

在candidate(记为x)发起竞选时,分别进行下面四步操作:

  1. 自增term

  2. 给自己投票

  3. 重置自己的election timeout

  4. 向集群中其他server发出RequestVote RPC

此时,candidate x的竞选结果可能有以下几种:

  • x竞选成功,成为leader

  • x竞选失败,其他candidate为leader

  • 没有任何candidate竞选成功

下面对candidate x的上述三种结果分别作出解释。

  • 第一种情况,x竞选成功,成为leader。当x获得了集群中大多数节点的投票之后,x成为leader,并开始向集群中其他server发出AppendEntries RPC,保持自己的leader地位。

  • 第二种情况,x竞选失败,其他candidate成为leader。在x等待集群其他server的投票结果时,收到了来自leader y的AppendEntries RPC,此时,

    • 如果y.term>=x.term,证明当前集群无需选主,leader y即为集群leader,x从candidate变为follower。

    • 如果y.term<x.term,则x拒绝该RPC,继续保持candidate状态。

  • 第三种情况,没有任何server竞选成功。原因可能为:

    • 如果 candidate y 和 candidate x 的 election timeout 差不多,而x和y各收到了集群少部分server的投票,没有任何一方满足多数原则,因此无人成功,坐等下一轮竞选。

    • 如果 candidate x 有机会获得 大多数 server 的投票,但因为资格不够被其他server拒绝 (后面会讲这种case)

举例如下:

如下图,假设集群初始化时,所有server端配置为图中给定值。

集群初始化server

显然,S2的election timeout最小,因此,S2会成为candidate,重置自己的election timeout,并将term number +1,投自己一票后向其他server发出RequestVote RPC,如下图:

S2超时发起竞选

上图中S1 S3 S4 S5的tern number都比S2小,S2大概率会得到大多数server的同意票,成为leader;

上面我们说过,raft是一个为了让集群中的server在log上达成共识的算法,那么,server在给一个candidate投票时,是如何判断投同意还是拒绝呢?除了判断 term number 的大小之外,是否需要满足其他的条件?

1.3 日志复制

在开始讲述日志复制之前,暂时假设集群当前已选好leader。

leader负责响应client发出的command的请求,同时,leader会将这些command写入log,并负责把log复制到各个follower,复制log到follower的请求称之为AppendEntries RPC,当leader认为该条log已被成功复制后,给client返回response,大致流程如下图。

client发起command(x=1), 大致流程

如果leader收到集群中大多数机器【写入log成功】的response,则该条log被认为是committed,此时leader可以返回client相应的response。Raft保证已经committed的log肯定已持久化且集群中每个机器最终都会成功写入该log。如上图,leader(S1)返回给client response时,集群中大多数机器(S1 S2 S3)都已经成功写入x=1,S4和S5可能因为机器已经crash/运行太慢没及时响应或者网络中数据包丢失等其他原因没有写入x=1的log,此时,leader会一直发送AppendEntries RPC, 直到log写入成功为止。

上面描述了集群写入log的大致流程,下面对每一步进行详细说明raft如何保证集群中每个server都成功写入该log。

分布式系统中,由于机器crash/网络等原因会引起各种各样的问题。raft通过制定一系列约束来保证日志复制的准确性,履行它【已经committed的log肯定已持久化且集群中每个机器最终都会成功写入该log】的承诺。

logs
  1. log由多个entry组成,每条 log entry 记录 log index、term number、command

    如上图,一个小方框代表一个entry,方框内第一行数字代表term number,第二行数字代表command;log index线性增长;

  2. leader 的 log 只会 append

    一旦server被选中作为leader,则所有的follower日志和leader保持完全一致,leader不会覆写/删除任何log entry。

  3. leader发出复制日志的AppendEntries RPC时,会带上上一条log的term number和log index

    • 为保证所有server log的顺序一致性

  4. follower中接收到新的 log entry 写入请求后,会比对当前最新log和AppendEntries RPC请求中的上一条log的term number和log index是否一致

    • 同第3条,为保证所有server log的顺序一致性

正如上述第2条所述,集群所有follower会以leader的日志为参考,与其保持完全一致,而raft又承诺已经committed的log肯定已持久化且集群中每个机器最终都会成功写入该log。如此一来,leader的选举显得尤为重要。

已经记录了log index,为啥还要记录term number呢?

1.4 选主还需哪些约束?

集群初始化时,选举leader相对比较简单,每台server都没有log,无论哪台server被选为leader都可以。然而实际上,分布式系统中,机器crash并不罕见,网络的延迟等因素同样不可忽视,集群运行一段时间后,重新选举leader的情况更为常见。

⚠️ 发起选举情况可能原因有两种:leader宕机,未发出RPC;leader没宕机,网络原因follower未在election timeout时间内收到S1发出的RPC。总之,发起选举的follower认为当前集群处于无leader状态,需要重新选主。

1.1小节讲述了大致的选举流程,更多侧重集群初始化选主,并没有提及发起选举的candidate 机器log的状态对于选举结果会有何影响。依然用下图举例,由上到下我们对server一次编号S1-S5。

集群当前各server log

由1.2小节可知,log成功被follower复制的条件之一就是上一条log复制成功,因此上图中S2 S4 S5状态是可能会存在的。下面假设一下几种可能情况,分别分析。

  • S2 election timeout

    log index 为6和7的已经成功写入大多数机器,状态为committed。假设S2可以成功被选举为leader,集群中follower需要和leader日志保持一致,因此如下图所示,原本已提交的log index=6,7的日志会被覆写为(term=4, x<-1),原本已提交的log丢失,raft无法履行【已经committed的log肯定已持久化且集群中每个机器最终都会成功写入该log】的承诺。

    所以,S2不能够称为leader。为了让S2不能成为leader,需要每个server在投票时对比本机的log index和发起投票的candidate的log index。如果发起投票的candidate log index落后于本机,则反对。有了上述约束,

    • S1(如未宕机) S3 S5反对票

    • S2 S4同意票

    S2未能获得集群中大多数server投票,竞选失败。

S2 election timeout
  • S3 election timeout

    毋庸置疑,S3会获得所有server的同意票(若S1未宕机 S1也是同意票),顺利成为集群leader,成为leader之后,log index为8的entry仍会被一直重试写入其他server,直到成功。

  • S4 election timeout (情况同S2)

  • S5 election timeout

    log index为8的entry,在S5发起选举时还未成功写入集群大多数机器,这种情况raft不做出承诺仍会持久化,S5得票情况如下

    • S1(若未宕机) S3 的反对票

    • S2 S4和S5 的同意票

    所以,S5会被成功选为新leader,如果client发出x<-1的请求,集群中所有server中log index=8的entry会被写为S5中的值。

S5 election timeout

因此,要成为leader,需要具备以下条件:

  • term number不小于集群中半数以上server的log index

  • log index不小于集群中半数以上server的log index

1.5 成员变更

略略略,有空再填坑叭......

2 raft有什么不足吗?

对于log的写入顺序控制是否过于严格?

写入log流程是否可以优化?

还有哪些性能优化点?

上面的问题先埋个坑,下次再说叭...(如果有下次

3 参考链接

http://thesecretlivesofdata.com/raft/#home https://raft.github.io/

raft论文中详细介绍了每种RPC携带的信息,每种状态的机器记录了哪些信息。希望大家有空都看看,本文如有不对之处,欢迎各位大佬指出。

Last updated

Was this helpful?