分布式一致性协议Raft(一)

铺垫

一个设计良好的分布式系统,应具备四大特点:

  • 并行性能(parallel performance):任务能均衡高效地在多台机器上执行,无需过高的通讯和锁消耗。
  • 容错性(fault-tolerance):少部分机器的宕机并不会影响整体任务的完成,且宕机机器重启后有办法重新加入到工作中。
  • 冗余性(replication):重要的决定性的数据,如log,必须拷贝多份分布在多台机器上,以防某台机器宕机丢失数据导致系统无法正常运行。一个好的冗余策略应该考虑机架、网络、甚至地域的分散。一般保证冗余的策略有两种
    • 状态交换State transfer:各机器之间实时交换的所有状态。如一个K/V存储服务,机器之间定时交换各自存储的内容。
    • 冗余状态机Replicated state machine:各机器之间保持初始状态一致,随后交换增量的操作。通过使机器之间执行的操作一致,以达到状态的一致。如一个K/V存储服务,机器之间定时交换各自的增删查改,以保证执行的操作和操作的顺序都是一致的。Raft使用的就是该类型的冗余策略。
  • 一致性(consistency): 一致性意味着所有相同操作在不同机器上执行的结果是一致的。一致性有强弱之分,关于一致性我后续会出博客介绍的。

Introduction

Raft是斯坦福大学研发的分布式一致性协议,满足parallel performance, fault tolerance, replication, consistency
四大特点。早期的paxos一致性协议十分难懂,而且对工程落地实现不友好。Raft一改Paxos的晦涩艰深,具有低理解门槛、低实现门槛的特点。本文将介绍Raft论文前五个章节的内容。

Algorithm

Raft设计了一套共识算法,满足容错、冗余和一致性的需求。它的基本结构是单Leader和多Follower组成的集群,Leader和Follower之间通过Log记录状态变化。Raft的共识算法可以分解为三个子问题:

  • Leader选举Leader Election:当前Leader宕机后,Raft定义了自动选举新的Leader的机制,保证系统仅有一个Leader有效。
  • 日志复制Log Replication:Leader接收来自client的请求,生成对应的操作日志后,必须将日志复制到集群所有机器中,并且令集群中的机器接受Log中的改动,从而保证集群数据一致性。
  • 安全性要求Safety:新选出的Leader必须包含所有已提交的日志项,已提交的日志唯一确定,不会被覆盖,以保证Raft算法的完备性。

Raft采用RPC作为通信方式,主要有RequestVote RPC和AppendEntries RPC两种。除此之外还有Snapshot RPC,本文将不会介绍这种RPC。

这里分享一个本科老师介绍的Raft算法动画演示,比较直观地描述了Raft算法的几个主要过程,我感觉还是很不错的。
http://thesecretlivesofdata.com/raft/

Leader选举过程

在Raft集群中,机器节点被分为三类:

  • Leader:全局只有一个,负责接收client请求,日志同步的节点
  • Follower:收发Log和执行任务的节点,可转化为Candidate
  • Candidate:竞选Leader的节点

Raft将时间分割为不同的任期(Term),Term数字是连续的,每一轮新选举开始,Term都会增加1。每一个Term内,只能有至多一个Leader。Term是Raft的逻辑时钟,可以表示状态的新和旧。每一个机器都有当前任期(Current Term),如果一个机器的Current Term比别的小,它将服从任期大的机器,并以其为标准,如果它是follower,它的log和current term都将以任期大的机器为准,如果它是candidate或leader,它都将变为Follower并认为任期更大的机器才有资格当Leader。反之,如果另一个Candidate的任期比当前机器小,当前机器将拒绝该Candidate的竞选。

在这里插入图片描述

Follower是机器节点启动时的默认状态。Follower有一个超时时间为election timeout的计时器,当它长时间没有接收到来自Candidate的RequestVote或Leader的AppendEntries时,它将自增Term并启动选举并成为Candidate。

该Candidate会给自己投票,并且向集群中所有的机器发送RequestVote,然后等待这些机器发送反馈。每个任期,所有投票者都只会投票给最先接收到的请求(first-come-first-served)。同时,Candidate存储的日志必须至少要和follower一样新,否则投票者会拒绝投票。

等待期间有三种可能:

  • 获取半数以上的投票,该Candidate变为Leader并结束选举。
  • 遇到拥有更高Term的Leader通信请求,转化为Follower。
  • 选举超时,term+1,并开启新一轮选举。这里超时时间是随机设置的,一般为150-300 ms,这么做可以避免单次选举出现多个Candidate竞争投票的现象(split vote)。

可能有两个Leader吗?

这里需要讨论两种情况

第一种情况是两个Candidate获得同样多Follower的选票VoteCount(都未过半),该情况下两个Candidate都不会转化为Leader。它们将会使自己的Term+1并开启新的一轮选举。这里由于Election Timeout是随机设置的,所以很大概率有一个Candidate先重新选举,另一个Candidate后重新选举。两个Candidate连续地在相同任期内选举并获得相同选票的可能性非常小,所以第一种情况可以由重新选举来避免。

第二种情况是由于网络分区(Network Partition)等原因造成的,这种情况也被称为脑裂Split Brain。脑裂情境下是有可能存在两个Leader的。如果由于network partition,已经选举出了新的leader,但是旧的leader和其网络分区内的client仍在运行。因为旧的leader在minority set里面,因此即使它收到了client的请求,向其他server发送时也不会得到回应,因此不会commit,也就不会回复client。所以在该情况下,即使出现脑裂,脑裂期间的日志也不会被commit。

日志复制过程

Leader接收客户端请求,将操作写入log,并行地向所有follower发起AppendEntries RPC(包含state machine command+Leader的当前term+前一个日志的term+日志索引),通知follower们复制该log。随后follower将处理收到的RPC请求。如果follower宕机或者运行速度很慢,或者网络丢包,Leader会选择一直重复发送AppendEntries(哪怕已经返回给client结果)

follower收到AppendEntries请求后,首先会检查Term和Log Index是否与己方数据一致。如果一致,Follower会选择接受复制,否则拒绝该请求。Leader收到拒绝请求后,会选择继续发送AppendEntries请求,直到发现共同的Term和Index,此时Follower会选择从该日志开始一直往后覆写,直到与Leader的日志保持一致。具体如何对齐Term和Index呢?(打个※号) Leader对每个Follower都维护了一个叫 NextIndex 的变量,标记着Leader发送给特定Follower的下一个log entry应该从哪个Index开始,对所有Follower的起始NextIndex都是它自身的下一个Log,如果Follower表示日志与Leader不一致(拒绝请求),Leader会将NextIndex回退一位,并重新发送,一直递减-发送到某一个nextIndex的值,使得Leader和Follower的Log能match上,结束匹配,并将日志从该Index开始一直往后覆写。

如果Leader的日志复制,接收到集群中超过一半Follower的复制完成消息,则可以认为该操作已在集群中达成共识。此时Leader会将日志Commit并返回客户端结果(注意,此时未返回success的日志仍然会继续重试以保持最终日志一致性)。Leader会保存提交日志中最大的index号,在AppendEntries时,Follower可以了解到哪些Log已经被Commit,然后会执行这些commit的Log中的指令,并写入本地机器。

一般来说Leader和Follower得日志复制能保持集群的一致性,但当Leader崩溃,Leader和Folower的日志可能就会出现不一致。Follower可能会丢失一部分来自Leader的日志信息。如下图的场景,其中最顶端的是Leader,它将在第8个Term成为Leader,但当前它的状态与其余a-f这几种可能出现的case的日志状态都不一致。

  • 在a跟b中出现了丢失日志的情况,a的index = 10 和b的index = 5-10的日志丢失
  • 在cd中出现了未提交的日志,c中index=11,d中index=11-12都是未被majority承认的改动
  • 在e中既出现了未提交日志(index 6-7),也出现了丢失日志(index 4-10)
  • f的情况比较特殊,它在第二个term成为Leader,收到了部分Log后一直没有commit,然后马上重启了,进入term3成为Leader,然后收到了部分Log后又无法commit,最后崩溃进入Term4。
    在这里插入图片描述

为了避免上述几种不一致的情况,Raft算法规定Follower必须被Leader的日志覆写,最终保持全局一致。上文中打※号的部分详细描述了具体做法。

安全性

尽管Raft规定Follower的日志强制覆写,但这依旧不足以保证系统的一致性。Raft论文中提出了五个Rule:

  • 选举安全性(Election Safety):每个Term最多有一个Leader被选上。
    • Raft 规定一个任期内,Follower只能投一次票
    • Candidate的日志必须跟Follower一样新,否则会被拒绝
    • 只有获得超过半数投票的Candidate才能变成Leader
  • 只准Leader添加(Leader Append-Only):一个Leader不会覆写自己的Log或删除自己的Log,它只能追加新的Log
    • Follower不能自主处理Commit和Write
    • 只有已经Commit的Log才能被执行写入状态机
    • 进行选举时,新的Leader日志必须包含所有已提交的日志
  • 日志匹配(Log Matching):拥有相同Index的日志一致,拥有相同Index日志之前所有的日志都一致。
  • Leader完备性(Leader Completeness):拥有最新**提交日志(注意不是所有日志)**的candidate才有资格成为Leader。
    • 投票过程中,如果一个Candidate的日志比Follower的commit日志旧,投票会被拒绝
    • 因为日志提交需要过半投票,比这个commit日志旧的Candidate肯定无法获得过半选票。
  • 状态机安全(State Machine Safety):一旦某个server将某个日志项应用于本地状态机(which means已经提交过了),以后所有server对于该偏移都将应用相同日志项。这使得所有机器都有相同的日志执行顺序。
    • Leader只能commit当前Term的日志,旧任期的Term只能通过当前任期的Term来间接完成commit。

这五条Rule都是针对Leader崩溃的场景,如果Follower和Candidate崩溃就相对简单了。Leader会不断尝试发送AppendEntries,直到Follower或Candidate恢复,然后覆写崩溃期间的数据。Raft的RPC由于幂等,哪怕Follower接收重复的请求也不会有任何问题。

时间要求

Raft系统中,各流程的时间长短对系统的可用性至关重要。这里有个两个时间长短不合理的例子:1. 一个Leader非常不稳定,不定时会自动重启,但RPC消息传递的速度比Leader崩溃重启的时间长,这也意味着没有新的Candidate会赢得选举,但这个不稳定的Leader会拖垮系统。2. RPC消息传输时间比electionTimeout长,Candidate甚至还没拿到选票就重新开启下一轮选举了,Leader election永远无法结束。

Raft有如下规定:

消 息 传 输 时 间 b r o a d c a s t T i m e < < 选 举 超 时 e l e c t i o n T i m e o u t < < 平 均 差 错 发 生 间 隔 时 间 M T B F 消息传输时间broadcastTime<<选举超时electionTimeout<<平均差错发生间隔时间MTBF broadcastTime<<electionTimeout<<MTBF

一般来说,传输时间在0.5ms到20ms之间,electionTimeout应该比传输时间高一个量级,在500ms左右,最后MTBF的量级通常在月甚至年的级别,能轻易满足要求。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值