Part 03:Raft论文翻译-《CONSENSUS: BRIDGING THEORY AND PRACTICE》(基础Raft算法)

3. 基础Raft算法

本章介绍了Raft算法。我们努力将Raft算法设计的更容易理解;第一部分描述了我们的可理解性设计方法。接下来的部分描述了Raft算法本身,并包括了我们为可理解性所做的设计抉择。

3.1 为可理解而设计

我们在设计Raft算法时有几个目标:它必须为系统建设提供一个完整和实用的算法基础,从而显著减少开发人员所需的设计工作;它必须在所有条件下都是安全的,在典型操作条件下可用;它必须对常规的操作保持高效。但我们最重要的目标,也是最困难的挑战,是可Raft算法的可理解性。必须使得大量读者能够轻松地理解该算法。此外,必须能够直观的指导开发者进行算法的工程化开发,以便系统构建者能够实现面对问题时所采取的不可避免的算法扩展。

在Raft的设计中,我们不得不面临在多种可行方法中的抉择。在这些情况下,我们基于可理解性来评估替代方案:评估解释每种替代方案有多难(例如,它的状态空间有多复杂,它有复杂的隐含内容/信息?),并试图评估读者要完全理解Raft算法及其隐含的信息会有多容易呢?

我们认识到,这种分析具有高度的主观性,虽然如此,我们还是使用了两种可行的技术来进行评估。第一种是问题解构(将一个大问题分解成多个小问题),例如我们将Raft算法整体上拆分成了Leader Election、Log Replication和Safety三个部分。第二个方法是通过减少需要考虑的状态来减少状态机的状态空间,尽可能使得共识算法具有有条理并且消除其中的不确定性。具体来说,Raft算法不容许日志中具有空洞(空洞就是在某个条目上没有相应的内容,而后面的条目却有内容,这是Paxos算法的一个缺点),而且Raft算法限制了日志产生不一致的可能性(通过Leader选举的限制以及只有Leader作为主数据源的限制,保证了Follower之间不会存在日志内容的互相传输,只要与Leader保持一致就好,减少了各个服务器上日志不一致的可能性)。虽然我们极力的避免算法中的不确定性,但是有的时候我们也通过这种不确定性来增强算法的可理解性(这就是以可理解性为首要目标的表现),例如,随机的方法引入了不确定性,但是这可以减少状态空间(通过随机的方法来避免处理系统中出现的需要处理多种可能的问题,如在Leader选举的时候,不同的Candidate通过超时来进行下一轮选举的发起,这样避免了Raft算法来处理每个算法无法胜选的情况)。

3.2 Raft概览

Raft算法是一种管理第2.1节所述形式的日志副本的算法。图3.1总结了算法以供参考,图3.2列出了算法的关键属性;本章其余部分分段讨论。

Raft首先选择一个服务器作为Leader,然后让领导者完全负责管理系统中的日志副本。Leader接受从客户端Client获取的日志条目(Log Entry),在其他服务器上复制它们,并告诉服务器何时将日志条目应用到其状态机是安全的。通过Leader管理这种方式简化日志副本的管理。例如,Leader可以在不咨询其他服务器的情况下决定在日志中放置新条目,并且数据以简单的方式从头Leader流到其他服务器(Follower或者Candidate)。Leader可能发生故障或与其他服务器断开,在这种情况下,一个新的Leader会被选出来。

通过基于Leader的方法,Raft算法将共识问题分解为三个相对独立的子问题:

  • Leader Election(Leader选举):当集群刚启动时,或者当前Leader失效时,集群中必须有一个新Leader会被选出来;

  • Log Replication(日支复制):Leader必须接收Client发出的log entry(log entry存储Client发出的请求/命令,这里将log entry等同于Client的请求/命令),并且要将这个log entry在集群中的其它服务器上进行副本的备份,强制其它服务器上的log以Leader上的Log为准;

  • Safety(安全性保障):Raft算法最重要的安全性就是状态及安全性(如图3.2所示),如果系统中有任意一个服务器已经apply一个log entry到自己的状态机中,那么其它服务器不会在相同的log index处apply不同的log entry(这个是由Raft算法通过Log一致性来保证的,具体的证明在文章后面)。这个保证由一个算法中提出的一个在Leader选举是的额外的限制条件来保障(详见第3.4小节)。

在介绍了Raft共识算法之后,本章讨论了可用性问题和系统中计时的作用(第3.9节),以及在服务器之间Leader转换的可选扩展(第3.10节)。

Raft算法相关特性
图3.2的说明:
Raft算法的关键性质:
(1)Election Safety(Leader选举的安全性,所谓的安全性就是系统中不会发生不该发生的事情):在某一个特定Term(这里就叫任期吧)内,最多有一个Leader能够被选出(那么不该发生的事就是一个任期内有多个Leader被选出,在Raft算法中,这是不会发生的);
(2)Leader Append-Only(仅追加):一个Leader仅仅会在它自己的Log追加Entry,绝不会删除或者覆盖已有的Entry(这是Raft算法能够实现单向数据同步的保证,试想,如果Leader的Log内容可以随便的改动,Follower不敢从Leader同步,因为不知道同步过后会不会又变了。而且,这个属性的基础来自于Raft算法有一个保障,那就是当选的Leader的Log一定是最完整、最新的,详细算法会在后面内容中叙述并证明);
(3)Log Matching(Log匹配):如果某两个服务器节点中的Log Entry有相同的Term以及Index,那么这两个节点的Log在Index之前的所有Entry内容一定相同;
(4)Leader Completeness(Leader完整性):如果某个Log Entry已经在某个Term(任期)被commit,那么这个Log Entry一定会存在于以后所有任期的Leader的Log中;
(5)State Machine Safety(状态机安全性):如果某个服务器已经在其状态机中Apply某个Log Entry,那么其它的服务器绝不会在相同的Log Index上Apply其它的Log Entry。也就是说,在所有状态机的Log上,只要是相同的Index,其对应的Entry的内容一定是一样的。
image.png

图3-1给出了Raft算法中关键的元素、操作以及原则,因为整张图太大,下面切分这个大图为4个小图来说明。


image.png

图3-1 State(状态)
1.Persistent state on all servers(在所有服务器上持久化State)
(在回复RPC请求前更新本地持久化存储内容)

  • currentTerm,这台服务器已知的最新的Term,集群启动时默认为0,后面单调递增;
  • votedFor,
  • log[],每个服务的log,每个log有若干个log entry,每个log entry就是一个客户端的请求/命令,这个请求/命令将在服务器的状态机上apply,这个log的每一个entry还会对应一个Term,就是这个log entry所属的Leader的任期,Log的索引初始值是1;
    2.Volatile state on all servers(每个服务器所具有的不稳定状态)(不稳定就是说可以丢失,比如宕机丢失)
  • commitIndex,这个服务器已知的,已经被commit的,最新的(也就是最大的)log entry的index,初始值为0,单调递增;
  • lastApplied,这个服务器在自己的状态机上apply的最大的log entry的index,初始值为0,单调递增;
    3.Volatile state on Leaders(Leader节点上的不稳定状态)
    (在选举后重新初始化)
  • nextIndex[],对于每一个服务器,Leader服务器节点都会保存自己下一次需要发给每一个其它服务器的Log Entry的index,Leader胜选后会进行初始化,初始化的值是这个Leader最新的Log Entry的index+1;
  • matchIndex[],对于每一个服务器,Leader服务器节点会保存其它服务器已经replicate的最新的log entry的index,初始值是0,单调递增;</pre>

<center style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0px; color: rgb(192, 192, 192); text-decoration: underline;">图3.2-2 RequestVote RPC</center>

image.png
  1. RequestVote请求,这是一个RPC请求

(candidate调用该请求来收集选票)

  • Arguments(参数)

    • term,candidate的任期Term;

    • cadidateId,candidate的ID;

    • lastLogIndex,candidate的log里面最新的log entry的index;

    • lastLogTerm,candidate的log里面最新的log entry所归属的任期Term。

  • Results(返回结果)

    • term,任期,回复这个RPC请求时,响应的那台服务器当前所处的任期Term;

    • voteGranted,投赞成票,当这个结果参数为true的时候,说明这个投票者同意选举该candidate为下一任Leader,反之,则是拒绝。

  • Receiver implementation(该RPC接收者的处理逻辑)

    • 如果接收到该RPC请求的服务器当前所处的任期Term大于candidate的RequestVote请求中携带的term这个参数,那么说明candidate的发起的请求晚于当前的Leader,或者当前已经有新Leader被选出了,又或者这是一个延迟收到的请求,那么这个服务器将会回复false(也就是在Results的返回值中携带voteGranted=false)以示拒绝candidate的本次选举请求;

    • 如果接收到该RPC请求的服务器当前存储的voteFor里面是空或者是某个candidateID,而且这个candidate的log和当前这个服务器的log一样新或者比当前服务器的log还新,那么这个服务器就会投票给这个candidate。(这个log的新旧比较就是根据最新的log entry的index来比较的)。

image.png

3.AppendEntries RPC,这是一个RPC请求,用于Leader向Followe同步log entry,Leader通过这个RPC请求要求Follower追加log entry以实现log的同步

(被Leader调用以用于进行log entry的备份,这个请求也会肩负心跳检查的功能)

  • Arguments(参数)

    • term,任期,这个Leader所属的任期Term;

    • LeaderId,Leader服务器的ID,当前Leader的ID,Follower可以根据Leader的ID将用户请求转发给正确的Leader;

    • prevLogIndex,紧接在新log entry之前的那个log entry的index;就是待插入的log entry之前的那个log entry的index;

    • entries[],需要在某个Follower服务器上同步的log entry的集合(如果为空,这个RPC请求仅仅作为心跳检测来用,这里面可以携带多个log entry,以提升log entry的复制效率);

  • Results(返回值)

    • term,任期,接收到这个RPC请求的服务器当前所在的任期,用于给Leader更新自身相关的信息,如Leader存储的关于这个Follower的信息;

    • success,成功标志,如果接收到这个RPC请求的服务器当前的prevLogIndex和prevLogTerm与这个RPC消息中携带的匹配,那么返回true;否则,返回false;

  • Receiver implementation(该RPC接收者的处理逻辑)

    • 如果接收到该RPC请求的服务器当前所处的任期Term大于Leader的AppendEntries这个RPC请求中携带的term这个参数,那么说明这个Leader已经是无效的Leader,则会回复false,表示拒绝更新log entry;

    • 如果接收到该RPC请求的服务器当前的prevLogIndex和prevLogTerm与这个RPC消息中携带的不匹配,则说明这次log entry更新不合适,这个服务器也会拒绝更新,并回复false;

    • 如果接收到该RPC请求的服务器当前的prevLogIndex和prevLogTerm与这个RPC消息中携带的匹配,但是相应的log entry已经有内容了,那么就删除当前的log entry,用RPC消息中的log entry更新相应log entry;(这儿就体现了在Raft中,log entry的内容以Leader的为准)

    • 如果接收到该RPC请求的服务器当前的log entry为空,那就直接更新(用这个RPC中的entries[]的内容);

    • 如果接收到该RPC请求的服务器当前的commitIndex小于Leader的leaderCommit(个人猜测:整理的LeaderCommit没有在RPC消息中携带,是不是根据entries[]这个参数推导出来?),那个这个服务器就要保存当前已经commit的log entry的index,也就是自己的commitIndex,commitIndex选取leaderCommit和本地最新的log entry的index之间的最小值(这儿有个疑问,这个服务器什么时候会commit这些log entry呢?)。“这里的疑问等看完后面的内容再更新”

为了说明prevLogIndex和prevLogTerm比较的方案,请看下图。

image.png

图中的Leader和Follower的任期都是3,Leader里面的log entry比Follower的新,所以Leader需要发送AppendEntries请求以使Follower同步这些log entry。这是,RPC请求内的prevLogIndex和prevLogTerm分别为i-1和3,当Follower接收到这个RPC请求时,其当前的log entry的index=i-1,任期为3,与RPC消息中的prevLogIndex和prevLogTerm匹配,那么Follower就可以使用entries[]里面的log entry来填充自己的i到n。如果Follower的最新的log entry的index(这里暂时成为FollowerIndex)不等于i-1,这里又分成两种情况,第一种,FollowerIndex<prevLogIndex,说明这个Follower的log还有缺失,那这个Follower会回复false,Leader会重新更新prevLogIndex、prevLogTerm以及entries[]并再次发起AppendEntries请求(后面的章节会详细介绍);第二章情况,FollowerIndex>prevLogIndex,说明Follower已经同步了prevLogIndex和prevLogTerm后面的log entry,但是,后面的log entry的内容是不是和RPC请求中相同?而且prevLogIndex和prevLogTerm这个log entry里面的内容是不是与Leader的相同?“这里的疑问等看完后面的内容再更新”

image.png
  1. Rules for Servers,集群中服务器需要遵守的规则
  • 所有服务器需要遵守的规则

    • 如果commitIndex>lastApplied,那么lastApplied增加(这里应该是一个一个增加),并将log[lastApplied]这个log entry在状态机上执行;

    • 如果RPC请求或者相应的回复中携带的term T>currentTerm,那么设置当前的term,也就是currentTerm=T,并立即更换角色为Follower;

  • Follower服务器需要遵守的规则

    • 必须回复candidate或者Leader发出的RPC请求;

    • 如果在预设的超时时间内没有收到Leader发出的AppendEntries请求或者未回复candidate发出的RequestVote请求,那么立即变换成candidate角色;

  • Candidate服务器需要遵守的规则

    • 一旦成为candidate,那就立即发起选举请求:

      • 增加term;

      • 给自己投票;

      • 重置选举计时器;

      • 发送RequestVote请求给其它服务器;

    • 如果收到了大部分其它服务器的赞成票,那么就变成了Leader;

    • 如果收到了新Leader发送的AppendEntries请求,变成Follower;(这里判断Leader是不是新Leader就是根据自己的Term和AppendEntries请求中的term进行比较)

    • 如果选举超时,重新发起选举请求;

  • Leader服务器需要遵守的规则

    • 获选后,先发送空的AppendEntries消息来通知胜选消除,然后通过发送AppendEntries消息来维持心跳,即使没有log entry需要同步也要定期发送维持Leader的地位;

    • 当收到client的command请求时,先把command保存到本地的log entry中,当本息的状态机apply这条command后再回复client(这里本地的状态机的apply的条件应该是集群中大部分的Follower已经正确保存了这个command);

    • 如果最后一个log entry的index大于某个Follower的nextIndex,那么就向这个Follower发送AppendEntries消息,消息中携带从nextIndex开始到最后一个log entry的index的内容;

      • 如果AppendEntries收到Follower的回复并有success=true,说明Follower成功备份了相应的log,那么更新本地存储的关于这个Follower的nextIndex和matchIndex的信息;

      • 如果AppendEntries收到Follower的回复并有success=false,说明Follower无法成功备份相应的log,那么Leader就要减小nextIndex并重试;

    • 如果存在N>commitIndex,而且大部分的Follower的matchIndex[i]>=N,而且log[N].term=currentTerm,那么设置commitIndex=N;(大部分的Follower都commit了某些日志后,那么Leader就可以认为这些log已经安全的存储了并被Apply,那么Leader就可以安全的认为这些log已经被Commit并在自己的本地信息更新已经commit的日志的信息,即commitIndex)

<< 上一章:Raft算法的目的
下一章:Raft算法基础 >>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容