摘要
raft是一种比paxos容易理解的一致性算法,实现起来比paxos简单许多。本文前部分描述算法的细节,后部分尝试探讨下该算法的原理。算法
算法描述
raft算法之因此简单的缘由之一是它将问题分解成三个子问题,分别是:安全
- Leader选举
- Log复制
- 安全性保证
概述
raft协议中每一个server都要维护一些状态,而且对外提供两个RPC调用分别是RequestVote RPC和AppendEntries RPC用于选举和log复制。
要想理解raft,其实就是搞明白:app
- leader和follower须要维护哪些变量,每一个变量的含义
- leader何时发送AppendEntries RPC,携带哪些参数,follower收到请求后作什么?leader收到响应后作什么?
- candidate何时发送RequestVote RPC,携带哪些参数,follower收到请求后作什么?candidate收到响应后作什么?
状态:

RequestVote RPC

AppendEntries RPC

raft全部的操做都是为了保证以下这些性质。ide
raft保证的性质
- Election Safety: at most one leader can be elected in agiven term.
- Leader Append-Only: a leader never overwrites or deletes entries in its log; it only appends new entries.
- Log Matching: if two logs contain an entry with the same index and term, then the logs are identical in all entries up through the given index.
- Leader Completeness: if a log entry is committed in agiven term, then that entry will be present in the logs of the leaders for all higher-numbered terms.
- State Machine Safety: if a server has applied a log entry at a given index to its state machine, no other server will ever apply a different log entry for the same index.
暂时能够先不看,须要知道的是raft全部的规则都是为了保证上面的这些性质,而这些性质又是保证raft正确的前提。设计
Leader选举
Election Safety性质说的在某个term中最多只能选出一个leader。
有以下这些规则:日志
- raft将时间划分为term,每一个term都有一个number,每一个term以选举一个leader开始。
- 每一个server有三种状态:leader, follower, candidate。

- 做为follower:有一个称为election timeout的倒计时,若是在倒计时内没有收到有效的AppendEntries RPC,将转换为candidate,增长本身的term number,投本身一票,而后经过RequestVote RPC通知集群中的其余server进行投票。当半数以上的RequestVote RPC返回true后,这个candidate将转换为leader。
- 某个server收到RequestVote RPC后如何肯定要不要投赞同票?同时知足如下三个条件则投赞同,RequestVote RPC返回成功:
- 在当前周期内尚未投过票
- candidate中term不小于本身的term
- "选举限制":想要得到投票,candidate的logs必须比当前follower的logs更up-to-date。如何比较两个logs的up-to-date程度?最后一个log entry的term大的更up-to-date, 若是term同样,index越大越up-to-date。(论文5.4.1节)
- 做为leader:周期性的发送AppendEntries RPC。
Log replication
Entry格式
Logs由Entries组成,每一个Entry包含一个term和命令,格式以下:

Entry若是已经肯定能够安全apply到状态机的状况下,将被标志为commited。看上面state图中,每一个server都维护两个变量commitIndex和lastApplied。这两个变量都是初始化为0,好比Figure 6中leader的commitIndex就是6,这个值会在
AppendEntries RPC中以leaderCommit参数通知followers修改本身的commitIndex。server
commitIndex和lastApplied有什么区别呢?lastApplied老是<=commitIndex,commitIndex代表的是到commitIndex为止能够被apply,lastApplied代表的是到lastApplied为止已经被apply。blog
什么状况下Entry能够被commit?知足如下两个条件:ci
- A log entry is committed once the leader that created the entry has replicated it on a majority of the servers.(leader将该entry拷贝到大部分server中)
- 不能commit term比当前leader的term小的Entry。这里不是很好理解,论文在5.4.2节给出了解释。
以下图:

(a):S1是leader(term=2),entry 2只拷贝到了S2就奔溃了。
(b):S5成为新的leader(term=3),而且接收了entry 3,可是还没进行拷贝也崩溃了。
(c):S1从新成为leader(只是term=4),而且将entry 2拷贝到了S3。若是没有条件2的限制,只看条件1,Entry 2已经被复制到了大部分的server中,就能够被commit了。那么问题来了,若是Entry 2被commit后S1又奔溃了,这时S5从新成为leader(根据上文给出的选举规则,S5最后一个Entry的term是3,能够得到S2, S3, S4和本身的投票,因此能够成为leader),并将Entry 3拷贝到其它的server(状况(d)),而且commit,这样以前commit的Entry 2就被覆盖了,这是绝对不容许的,已经被commit的Entry不能被覆盖。再次回到状况(c),这时若是S1不只复制了Entry 2还复制了Entry 4(term=4)(状况(e)),这种状况下同时知足条件1和条件2,因此能够commit Entry 2和Entry 4,由于和以前不一样的是,若是S1如今奔溃了,S5不可能成为leader(S5的最后一个Entry的term=3,S1, S2, S3都会拒绝投票,由于它们的logs更up-to-date),也就不可能出现commit的Entry被覆盖的状况。
这个图画的有点歧义,我总结下,(c)若是不考虑条件2的限制,可能会出现(d),(d)是不容许出现的。(c)若是同时考虑条件1和条件2,那么可能出现(e),(e)是合法的,毫不可能出现(d)这种状况。因此条件2是必要的。rpc
日志拷贝的过程
- leader接收客户端的Entry,将Entry添加本身的logs中。
- leader周期性使用AppendEntries RPC将新的entry备份到其它server。
- follower收到AppendEntries RPC后作什么?进行一致性检查。根据参数中的prevLogIndex,检查本身的log的prevLogIndex处的Entry的term和参数中的prevLogTerm是否相同,若是相同则将参数中的entries拷贝到本身的log中,不然返回false,leader收到响应后若是发现是false,调整参数而后从新发送AppendEntries RPC。至于怎么调整参数见论文5.3节。
不严格的正确性解释
论文中指出raft正确性已经使用TLA+ specification language进行了证实,顺便搜了下这个TLA+ specification language,由Leslie Lamport发明,用来验证设计的正确性的语言,这个Leslie Lamport也是NB的一塌糊涂,Paxos算法也是他设计出来的。我没有仔细研究严格的证实,只是以一种不严格的方式试图解释下为何raft能够保证一致性。
raft中全部的log都是由leader流向follower的,因此你leaader首先得保证拥有全部的committed log吧,这就是Leader Completeness属性。那么如何保证Leader Completeness属性呢。我认为如下这些规则保证了该属性:
- Entry须要被大部分server接收才能被commit。
- leader须要大部分server投同意票才能成为leader。
- "选举限制":想要得到投票,candidate的logs必须比当前follower的logs更up-to-date。
能够用反证法来证实:
假设Leader Completeness属性不成立,term T的leader(T) commit了一个Entry,可是新的term U的leader(U)没有包含该Entry。
leader(T)已经commit了该Entry,因此该Entry确定被大部分server接受了,leader(T)成为leader确定收到了大部分server的投票,那么一定存在一个server既接受了该这个Entry也投了leader(T)一票。显然该server包含的log比leader(T)的要up-to-date,因此和规则3矛盾,因此假设不成立,Leader Completeness属性成立。
保证了源头log的正确性后,拷贝过程当中也要保证和leader的log一致。Log Matching属性保证了这一点,该属性描述的是:若是两个server中logs中的某个index对应的log entry的term相同,那么这个index以及以前对应的log entry都应该保证同样。一致性检查规则能够保证该属性。
raft这些性质中最重要的就是保证State Machine Safety属性,该属性描述的是任何一个server在某个index apply一个Entry,其它server不会在该index处apply一个不一样的Entry。Leader Completeness + Log Matching能够推出State Machine Safety。