Raft是分布式环境下的一致性算法,它经过少数服从多数的选举来维持集群内数据的一致性。它与RBFT算法名称有点像,然而Raft算法里不能存在拜占庭节点,而RBFT则能容忍BFT节点的存在。Raft很是相似于paxos协议(参见个人这篇文章《paxos算法如何容错的–讲述五虎将的实践》),然而它比paxos协议好理解许多(由于paxos协议难以具体实现,因此zookeeper参考paxos实现了它本身的Zab算法)。一样,Raft有一个用GO语言实现的etcd服务,它的功能与Zookeeper相同,在容器操做系统CoreOS做为核心组件被使用。html
本文先从算法总体上说明其特色,再细说其实现。为方便你们理解,本文仍是以图为主,没有过多涉及算法的细节。Raft易理解易实现,是咱们入门分布式一致性算法的捷径!git
Raft协议的性能并不比paxos的各类实现更高,它的优势主要在于协议的可理解性好,且很是具有可操做性,很容易照着协议就能够实现出稳定、健壮的算法。论文做者在斯坦福和加州大学作过测试,对本科及研究生分别学习paxos和Raft协议课程后测验,在总分60分的测试里其得分以下图所示:github
从上图可见,Raft协议的得分(平均25.7)明显高于paxos(平均20.8)。我相信学习过paxos算法的人都有心得:很辛苦的理解后,一段时间后就彻底不记得细节了,至少我本人是如此。而Raft协议很是简单清爽,这个测试也很能反映问题。在测试后,做者还发起了一个调查问卷,询问学生这两个算法哪一个更容易实现?哪一个更容易理解?其答案以下图所示:算法
可见,这个带有主观性的调查问卷呈现压倒性优点:Raft是一个容易理解、容易实现的算法。数据库
Raft有不少语言的实现,包括C++、GO、Scala、Java等(详见https://raft.github.io/),但最有名的实现就是etcd了,它做为CoreOS生态的重要组件而闻名。咱们能够经过etcd的性能数据看一看Raft算法的实际表现。安全
测试集群由3台服务器构成,其配置以下:服务器
下面分别测试写和读的性能。写性能数据以下表所示:网络
Number of keys | Key size in bytes | Value size in bytes | Number of connections | Number of clients | Target etcd server | Average write QPS | Average latency per request | Average server RSS |
---|---|---|---|---|---|---|---|---|
10,000 | 8 | 256 | 1 | 1 | leader only | 583 | 1.6ms | 48 MB |
100,000 | 8 | 256 | 100 | 1000 | leader only | 44,341 | 22ms | 124MB |
100,000 | 8 | 256 | 100 | 1000 | all members | 50,104 | 20ms | 126MB |
而读性能数据以下表所示:架构
Number of requests | Key size in bytes | Value size in bytes | Number of connections | Number of clients | Consistency | Average read QPS | Average latency per request |
---|---|---|---|---|---|---|---|
10,000 | 8 | 256 | 1 | 1 | Linearizable | 1,353 | 0.7ms |
10,000 | 8 | 256 | 1 | 1 | Serializable | 2,909 | 0.3ms |
100,000 | 8 | 256 | 100 | 1000 | Linearizable | 141,578 | 5.5ms |
100,000 | 8 | 256 | 100 | 1000 | Serializable | 185,758 | 2.2ms |
在读性能数据中,Linearizable一致性高于Serializable,故性能稍差。其原始页面在这里:https://coreos.com/etcd/docs/latest/op-guide/performance.html。分布式
事实上,在算法的正常运行中与paxos并没有差别。而一旦leader宕机,从发现到从新选举出新leader、新leader开始工做的这段时间的长短,是影响性能的重要指标。下图中对5个节点构成的集群反复的让leader宕机,观察恢复的时间,其结果以下:
在上图中有如下几个关注点:
上图代表,增长随机化后,能够大幅减小选举的平均用时。下面的图代表,经过下降最短的超时时间,也能够减小宕机时间。Raft推荐的时间为150-300ms。
复杂的问题能够经过分解为多个简单的子问题来解决,Raft正是如此(paxos很难分解)。Raft首先定义本身是一个key/value数据库。那么,请求就分为读和写。Raft将问题分解为如下几个要点:
所以Raft将一致性问题转换为leader的选举上,以及leader与follower之间的数据同步。咱们先来谈leader与 follower之间的数据同步问题。每一次写请求会修改数据,读请求则不会,因此把leader收到的写请求看成一次操做日志记录下来,且同时把操做日志同步给全部的follower节点,而有一个最终状态数据库记录某一个key的最终值,以下图所示(事实上这与fabric区块链里,多条交易日志构成的世界状态数据库很是类似,详情请参见《区块链开源实现hyperledger fabric架构详解》):
上图中其步骤含义以下:
当一个集群刚启动时,全部的节点都是follower,follower只能被动的接收leader的消息并响应。此时通过一段时间若follower节点发现全集群没有leader,开始把本身做为leader的候选人向你们征询投票,此时该节点叫作Candidate候选人。若多数follower节点赞成后,则升级为leader节点。而leader节点有义务定时心跳通知全部的 follower节点,使follower节点知道此时集群中的leader是谁。以下图所示:
上图的状态变迁里,follower在一个随机定时器(例如150ms到300ms之间)内没有收到leader的心跳,则开始发起选举,而候选人就是本身,因此本身转化为Candidate,且本身首先投本身一票。若在投票未完成时,发现新的leader出现,则取消投票,由candidate转换为follower。
每次选举是一个任期,这个任期叫作term。每次任期有一个任期号,它是全局的、递增的,当网络中断时虽然会暂时不一致,但网络畅通后会很快同步,以下图所示:
如上图中,蓝色是选举期,绿色是产生leader后。若是不出现意外,这个leader会一直当下云,因此term周期会很长。出现宕机或者网络波动时,从新选举因而出现term2。在term3时也可能一直选举不出新的leader,此时极可能多个candidate发起了投票,票数被平摊后谁也没拿到大多数(因为每台follower的定时器时间是随机的,所以该状况发生几率很小,且发生后也能很快回归正常)。因而会进入term4。
leader须要把写日志同步到大多数follower后才能更新状态数据库,并向client回复写成功。若是没有获得多数follower的成功应答,leader会重复发送这条日志更新请求。下图中有8条日志3个任期5个节点,每条日志里除记录了操做行为外还记录了当时的任期:
上图中,绿色是第一个任期,其中3条日志条目中第3条日志y=9没有被第4个节点接收到。黄色是第2个任期。绿色与黄色任期内,全部的日志皆被多数节点收到,所以都是写入状态数据库的,这些日志的状态都是commited已提交状态。蓝色是第3个任期,其第8条日志x=4没有被多数节点收到,所以该日志不是committed状态。
leader与 follower之间的日志也可能存在不一致的状况,follower或者少了一些日志,或者多了一些日志,以下图所示:
上图中最上面一行是leader的日志,而follower的日志存在如下状况:
leader若是肯定多数机器收到日志,天然能够提交。若是新leader刚被选出来,它会试图把多数机器上保存的日志(即便它本身没有这条日志)–也就是前任的日志也提交,但这未必保证必定成功,以下图所示:
在上图中,d和e就是提交前任日志努力下可能致使的两种情况:
一般咱们把raft集群配置为3或者5个节点,特别是5个节点时能够容忍2个节点宕机。这给咱们平滑升级时带来了好处:1台台升级时仍然能够容忍1台宕机。但若咱们的集群原来是3个节点的组合,却改成5个节点,若是这个过程是不中止服务动态完成的,这可能出现问题,以下图所示:
在上图中,绿色的老配置只有一、二、3这三台server组成集群,而在蓝色的新配置里则在一、二、三、四、5这五台server组成的新集群。因而,存在红色箭头指标的点,在该点上,可能一、2这两台server根据老配置在它们2个中选出第1个leader,而三、四、5根据新配置在它们3个中选出了第2个leader。同一时刻出现了2个leader,这样数据就会不一致。
为了解决上述问题,Raft提出了一个共同一致状态,该状态处于老配置和新配置生效的中间阶段。首先,咱们设C(old)为老配置,而新配置为C(new),欲从C(old)状态置C(new),必须经历C(old,new)状态。其中,更新到C(old,new)以及C(new)时,仍然以复制日志的方式进行,即:先进行日志复制,当肯定多数节点收到该日志后,则该日志为commited已提交状态。以下图所示:
从上图中能够看到:
能够看到,Raft算法的核心就是leader选举以及日志复制。而日志的无限增加,必然带来性能问题,这是从工程角度必须解决的问题。日志表示的是过程,状态数据库表示的是结果;一样,咱们能够按期把某一时间点以前的日志作成状态数据库,或者称为快照,仅保留该时间点后的日志,这样就能够大幅减小日志的数量。以下图所示:
在上图中,原先的已经被提交的5条日志最终致使的状态是x=0&&y=9,故能够被快照替代,这便减小了日志量。
Raft还有一个很是形象的算法演示动画,包含了一致性算法的由来、leader的选举、隔离网络下的leader选举、日志的复制等场景,请打开RaftUnderstandable Distributed Consensus连接观看。
学习Raft算法有助于咱们理解分布式环境下的一致性解决方案,并且它确实比paxos好理解许多,能够做为咱们的入门算法。
(转载本站文章请注明做者和出处 陶辉笔记 ,请勿用于任何商业用途)