raft论文

2019-04-28  本文已影响0人  Damon_330b

概念说明

image

raft主要分成三个部分:

领导人选举

在系统初始化时,所有的节点都会被初始化为follower,leader会被选举出来。follower会周期性的接收到来自leader的心跳rpc,以维持follower的状态。若在选举超时时间内没有收到rpc,follower就会选举超时,该超时时间在一个范围内随机(如150-300ms)。超时后,follower就自增自己的currentTerm,成为candidate,并给自己投票,然后向其他服务器发送投票请求rpc,follower处理投票请求rpc时遵循先到先得的与原则。candidate会一直处于这个状态直到下列某一情况发生:

  1. 赢得大多数选票,成为leader
  2. 其他服务器成为leader
  3. 没有任何一个服务器成为leader
    如果集群中同时出现两个candidate,其中一个竞选成功,成为leader,发送心跳rpc,另外一个竞选失败的candidate在收到心跳rpc的term至少跟自己的term一样大时会转变为follower。
日志复制

以一个请求来说明日志的复制流程:
leader

  1. 客户端发送set a = 1的请求到leader节点
  2. leader节点在本地添加一条日志,本地有两条索引记录日志的提交和应用情况,committedIndex 日志的提交索引, appliedIndex 日志应用到状态机的索引。这一步日志还没有提交,两条索引还是指向上一条日志。
  3. leader向集群其他节点广播该日志 AppendEntires消息
    follower
  4. follower收到AppendEntires消息,在本地添加AppendEntires对应的日志,该日志还没有提交。
  5. follower节点向leader节点应答AppendEntires消息。
    leader
  6. 当leader节点收到集群半数以上节点AppendEntires的应答响应时,就认为set a = 1 命令成功复制,可以进行提交,于是修改本地committedIndex指向最新存储的set a = 1的日志,而 appliedIndex保持不变
  7. 提交之后,通知应用层该命令可以提交,此时会修改appliedIndex为最新的committedIndex
  8. leader节点在下一次给follower的AppendEntires请求中会带上最新的committedIndex索引,follower收到请求后会根据请求中的committedIndex修改本地的committedIndex

leader需要根据集群节点对AppendEntires的响应来判断一条日志是否被复制到半数以上节点。当leader收到半数以上的响应,就认为该日志已经复制成功。此时leader宕机时,后续新当选的领导人肯定是在已成功接收最新日志的节点中产生,还是能保证该日志被提交。

日志数据不一致问题:
Raft通过将将leader日志复制到follower节点,并覆盖follower节点中与leader不一致的日志。leader节点为每个节点存储了两个记录:

AppendEntries RPC 中包括:

状态的持久化存储和server的重启:

领导权转移:
Raft允许server将自己的领导权转移其他的server,有两种场景:

  1. 当前leader需要重启维护,当leader 选择先变成follower再下线,这段时间集群会出现选举超时,集群不可用,通过
    领导权的转移可以避免在这种情况。
  2. 当其他一些Server更适合作为leader 时,如leader的负载较高,如广域网部署,主要数据中心的延时较低,server 更适合作为leader。raft可以定时观察集群内是否有更适合的server最为leader,然后将领导权交给它。

为了选举能够成功,当前leader需要将自己的log entries发送给目标Server,保证目标server上持有所有提交的log entries,但后发起leader竞选,不需要等待选举超时。

当目标server失败时,当前leader就会中断领导权转移过程,恢复客户端请求处理。

集群成员变化
image
为集群增减成员是一个复杂的问题,如果通过下线的方式来修改配置增减集群成员会导致一段时间服务不可用,手动的操作步骤会带来操作失败额风险。为了避免这些问题,raft支持集群的成员的自动上下线,这些操作是集成到raft一致性算法中的。
任意的成员变更是非常复杂的,因此raft每次只允许集群中又一个成员的变化,多个成员的变化可以拆解为单个成员的变化.
当raft要将要移除集群中的成员时,它需要通过日志复制的机制将集群中配置由C old转换为转换为C new, 成员在收到配置后立即生效。 image

现在还有两个问题:

为了解决新成员加入时,成员需要追赶leader日志的问题,raft 引入了 non-voting server,等到日志同步完成时再开始加入集群。首先会引入round的概念,每个round开始时,leader将non-voting server少于leader的日志同步到non-voting server,round中新接收的日志会在下一个round同步。 若没有新日志发送到leader时,一个round开始会马上结束,进入下一个round,当进行round的次数超过阈值时,leader就将新的server加入到集群

当前leader的移除
当要下线集群的leader时,首先客户端会发送一条C new的配置请求,C new会以日志的形式复制到集群大多数节点 , 只有当该日志提交之后,leader才可以转变为follower再进一步下线,只有当C new复制到大多数节点,集群才有可能从C new的成员中选举出leader。leader才可以转变为follower,此时C new的成员会选举超时,从而选举产生 leader。旧 leader的不可用到新leader的产生的这段时间系统是处于不可用的状态。
下线成员对系统的扰动
当下线非leader节点时,该节点就收不到新的配置C new,也就不知道自己是否下线,此时leader上新的配置生效之后,就 不再给将要下线的节点发送heartbeat。该节点就会超时并发起选举,选举会扰乱当前系统leader的工作,由于周期高于当前leader,leader就会转变为follower, 发起选举的节点不在系统内不会当前选,系统内会重新选举出一个leader。所以 raft提出了一个已解决方案:
为leader竞选阶段引入一个新的阶段,pre-vote,candidate发起投票之前会询问其他节点自己的日志是否足够新的来竞选leader。但pre-vote的引入并不能解决上述问题。

image
s4为leader,C new log提交之前,,s4是要下线的节点,s4收到新的配置生效后就不会给s1发送heartbeat,s1选举超时,自增term发起选举,server扰动依然存在,日志还没有复制到其他节点,此时pre-vote在此时不能阻止s1发起选举。
Raft使用心跳来判断集群中是否有正常工作的leader。所以一个leader正常工作的集群的其他节点不应该发起选举,当集群中的节点能够正常收到heartbeat时(在最小的选举timeout时间内),就不会接收新的选举请求,即使接收到更大的周期号。正常的选举过程不会收到影响,因为在leader宕机时,集群所有节点需要在经历选举超时后才会开始leader选举。
前面提到的领导权转移机制跟上述的保护机制有冲突,领导权转移机制是会重新发起leader选举不需要考虑是否有选举超时,此时集群的其他节点需要处理这种RequestVoteRPC即使它们认为集群中的leader是正常工作的,因此RequestVoteRPC需要带上特使的flag来标明这种特殊的行为。
安全性

前面描述的选举和日志复制机制还不能完全保证每个状态机都能根据相同的日志按照相同的顺序执行命令。例如一个follower因为网络问题错过了多次的日志复制,然后网络恢复,集群leader宕机,follower当选为leader,该follower缺少上一个leader 提交的日志,就会导致这些日志被新的leader覆盖。

日志压缩

随着raft处理客户端请求的增长,日志记录也会越来越长,占用的存储空间也会越来越多,也会花费越来越多的时间进行日志重放。但在一个实际的系统当中,存储空间不可能没有限制,因此采取一种机制来丢弃部分日志记录势在必行。
快照是最简单的进行日志压缩的方式。每个节点独立的生成快照,通过将整个状态机的状态都写入快照然后存储到稳定的存储介质上方式,可以允许快照点之前的所有日志可以被删除。Raft还会在快照中保存一部分元数据,也就是快照可以替换的所有日志记录的最一条(最后一条状态机执行的日志记录),这条日志是用来做AppendEntries时的日志一致性检查的。同时为了保证集群成员变化,快照中还会保存应用最后一条日志时的配置文件。

上一篇 下一篇

猜你喜欢

热点阅读