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.
针对上图的内容,包括有这几个步骤:
- 由Consensus Module接收来自Client的Command
- 将从Client接收到Command数据,写入到本地的Log中;并同其他Server的Consensus Module交互,确保其他Server的Log中按照相同的顺序写入Command数据。一旦Command数据被充分地复制,就认为该Command数据被提交(committed)了。
- 每个Server的State Machine按照Log中的顺序挨个地处理已经提交的Command数据
- 返回给Client输出结果
Raft算法基础
Raft是一种管理Replicated Log的算法。用一句话来介绍Raft如何来管理Replicated Log:
- 先选出来Leader
- 由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
的增加。Term
跟Epoch
类似,每个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算法基础中,可以分为如下三个相对独立的子问题:
- Leader Election选主,初始化一个新的集群或者Leader挂掉了之后再重新进行选主
- Log Replication日志复制,包括接收来自Client的log entries,并复制到各个集群里去
- 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并发起选主。
选主的过程:
- 将自己的
currentTerm
做加1操作,并将自己的状态变成Candidate - 给自己投票,同时并行地往集群里其他的Server发起RequestVote RPC请求
- 然后持续作为Candidate状态直到如下三个事件中的任何一个发生:
- 自己赢得了选举(自己赢)
- 其他的Server变成了Leader(别人赢)
- 选举超时到了,但是还没有任何一个Server赢得了选举(没人赢)
上面的选举超时时间(Election Timeout)是很有讲究的,若设置不好,很有可能都陷于上面的第三种情况(==split vote==),一直产生不了获取多数票的Leader,因此为了规避,采取在一个区间[150ms ~ 300ms]里随机选择一个值作为选举超时时间。
Log Replication日志复制
Log Entries 日志条目
在日志复制模块里,首先明确复制的是什么内容,即本小结里需要阐述的log entries的内容。
从上图可以得到如下的信息:
- log是由编号的entries组成,该编号叫做log index,用来表示该entry在整个log的位置
- 在每个log entry里,包括有2部分内容,第一部分是term信息,第二部分是command数据,该command数据需要用来交互并提交到State Machine里去
- 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
非常重要的两条属性
- If two entries in different logs have the same index and term, then they store the same command
- 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也是相同的。
可以通过如下的两个来保证上面的两条属性:
- leader一旦创建了log entry,就不能挪动其位置
- 每次leader在发送AppendEntries的时候,除了带上新创建的log entry外,还带上前面一个log entry的log index、term信息(==prevLogIndex, prevLogTerm==)。若Follower接收到AppendEntries之后,发现有冲突则拒绝掉
异常处理
对于日志复制过程中,若正常情况,都能满足Log Matching Property需求。但是若leader或者follower宕机的情况下就比较复杂了,因此需要进行异常处理。
上面这张图的解读如下,若leader的状态如最上面的log所示,那么各个follower会出现的状况有:
- (a)(b)的情况,即follower会丢失一些log entries.
- (c)(d)的情况,即follower会多一些uncommitted log entries
- (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==
具体操作步骤:
- 找到leader和各个follower在log上一致的log index信息,即从这个log index之后的数据都是非一致的
- 将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论文学习 - 基础算法的主要内容,如果未能解决你的问题,请参考以下文章