分布式系统基础-Raft算法

分布式系统基础-Raft算法

注意:这篇文章是我在阅读Raft算法的论文之后的一些想法,记录下来.其中可能有些地方表述的不准确,也可能我理解有误.所以请各位还是先阅读Raft算法论文<<In Search of an Understandable COnsensus Algorithm>>然后再读这篇文章.防止我误人子弟.

简介

学习分布式系统的朋友应该都知道,分布式一致性问题一直是一个很难的问题.针对这个问题,最早提出的算法是Lamport大神的Paxos算法,但是这个算法由于难于实现,以及不易于理解,而被人诟病.基于此,Standford大学的博士Diego Ongaro和John Ousterhout就发明了Raft算法.

Chubby的作者说,世界上只有一种一致性算法,那就是Paxos算法,其他的算法,不是Paxos算法的简化版,就是错误的.我觉得Raft就是Paxos算法的一个简化版本.事实上它也确实是.

Raft的整体架构,我认为是结合了Primary-Backup 模式以及 State Machine模式,关于State Machine模式,前面我在一篇文章中,专门介绍过,文末我会附上这篇文章的链接,这里也不再介绍了.而Primary-Backup模式,也早已被大家所熟知,它是一种主从结构,平时只使用主节点来响应客户端请求,然后主节点可以有好几个备用节点,一旦主节点挂掉,备用节点就顶上.

关于Primary-Backup模式以及State Machine的介绍,请大家查看文末的链接中,Fault Tolerant Services这一篇文章.

在Raft算法中,就有这么一个专门的Leader节点,这点便是对Paxos算法进行的简化,在Paxos算法中,可以有多个Proposer(Proposer相当于这里的Leader节点).而且Leader有且只能有一个.其他的节点均为Follower节点.在Leader节点以及Follower节点中,均存在一个状态机,Leader节点在接收到客户端的请求之后,会先保存到本地的Log Entry队列中,然后将其发送给Follower节点,一旦收到多数Follower节点的响应,Leader节点便会告知客户端操作成功并返回相应的结果,然后提交Log Entry并通知Follower节点提交Log Entry.写请求是上面的那个流程,而如果是写请求呢,则直接从Leader节点的本地状态机中读取相应的数据并返回给客户端.

我们这么一看,实际上它就是一个结合了Primary-Backup模式以及State Machine模式.

细节我们会在后面的小节中讨论.

Raft算法中的一些概念

在上一小节中,我们大体介绍了Raft的工作流程,但是,这是不够的.我们还需要详细考虑其实现.

那么在此之前,我们先了解一下Raft算法中的一些概念.这些概念我们不会进行翻译,因为我当初在读这篇论文的时候,翻译这些概念的时候,让我有点懵.

我们先说Log Entry.我们把客户端请求执行的命令,放到一个队列中,其中每一条要执行的命令,都把它封装一下,加上一些其他数据,变成了一条Log Entry.

Log Entry中,存储的除了要执行的命令,还有term.terms是Leader的标志,表示是哪个Leader提交的命令.因为在这个Log Entry队列中是还存储之前的Leader提交的命令的,所以需要term.不要误会为可以同时有多个Leader.当时,term还有其他作用,我们后面会介绍.

Log Entry队列中,还有一个其他的概念,是index.它表明Log Entry在这个队列中的位置.就跟数组中的元素在数组中的位置一样.具体的用处我们也会在后面介绍.

Raft算法中节点的三种状态

在Raft中,存在以下几种类型的节点:

  • Leader.Leader负责接收客户端发送的请求,通知Follower节点以及响应客户端.
  • Follower.集群中除Leader节点之外的节点,都是Follower节点.Follower节点负责维护一个状态机,接收Leader发送来的Log Entry以及其他命令.
  • Candidate.当集群中的Leader不可用时,Follower节点就会转换成Candidate状态直到选举出一个Leader.

Raft算法的具体过程

我们在上面粗略的介绍过Raft算法的处理过程.这里我们详细的介绍一下.

首先,集群启动时,选举出一个Leader节点,然后其他的节点都处于Follower状态.然后,客户端随机选择集群中的一台机器,并访问它,如果这台机器是Follower节点,那么它会拒绝客户端的请求并且告诉它Leader节点的访问地址.当Leader节点收到客户端的请求之后,正如上面我们说的那样,它会生成一个Log Entry并保存在一个队列中,然后将这个Log Entry广播给全部Follower节点,当收到多数Follower节点的响应时,就会给客户端回应并且提交这个Log Entry.提交一个Log Entry的意思是,将其对应的命令应用到状态机.在这里,Leader节点还会同时将这个提交Log Entry的命令广播给其他节点.

当然Leader节点也可能会由于网络延时的问题,而没有收到多数Follower节点的响应.那么Leader节点就会对那些没有收到响应的节点,重复发送请求,直到收到响应.

当Follower节点收到Leader命令的提交Log Entry的命令时,就会将其对应的命令应用到状态机.并且,在其Log Entry队列中的index小于需要提交的Log Entry的index的Log Entry,也会同时被提交.

当Follower节点在一定时间内没有收到来自Leader节点的请求时,便会认为Leader节点出故障了,于是,便会把它自身变为一个Candidate节点,进行选举过程.如果其他的大多数节点同意让它做Leader节点,或者收到了其他新任命的Leader的消息,那么它便会停止选举过程,重新转换为Follower节点.

所以,为了防止Leader其实并没有出现故障,而是由于一段时间内确实没有收到请求,而没有给Follower节点发送请求,Leader节点需要定期发送给Follower一个请求,表明它其实还在正常工作.

同样,在选举过程中,为了让其他的Candidate知晓已经选举出来了Leader,所以Leader需要在上任的时候,给其他的Candidate发送请求,表明它是新选出来的Leader.

各个节点上需要存储的数据

Raft算法中的RPC

从上面的过程中,我们可以看到,至少需要两种RPC操作:

  • AppendEntries RPC: Leader在将Log Entry广播给Follower时,便是用的这种RPC.
  • RequestVote RPC:当Candidate需要其他的Candidate协助它被选举为Leader节点时,就需要向其他的Candidate节点发送这种RPC.

关于这两种RPC的具体细节,这里我直接就贴出来了,论文中描述的已经很详细了.

还有其他的一些操作,这里也一并列出了:

Raft算法中Leader的产生

每一个节点上,都需要存储一些配置,一些数据,比如,当前是哪个Leader主事,用currentTerm表示.在Candidate节点A进行选举时,它需要提出一个更大的term,并发送给其他的Candidate节点B和C.如果Candidate节点B和C发现Candidate A提出的term比它当前维护的currentTerm大,并且Candidate B和C的voteFor为空或者它自己,并且Candidate B的Log Entry队列中的记录比他的更新,就告诉Candidate B,我选举你为Leader啦.如果上述三个条件中的任意一个不满足,则不选举其为Leader.

那么上面说的记录更新是什么意思呢?

  • 如果Log Entry队列中的最后一个Log Entry的term不相同,那么具有更大的term的那个Candidate中的Log Entry队列更新.
  • 如果Log Entry队列中的最后一个Log Entry的term相同,那么Log Entry队列更长的那个Candidate更新.

如果集群中仅有三台机器A,B,C,根据上面的过程,Candidate B就可以成为Leader了.因为加上他自己同意他自己成为Leader,已经达成集群中多数机器同意的条件了,那Candidate B现在就成为Leader了.

如果Candidate B在超时的时间范围内,既没有成为Leader,又没有其他的Candidate告诉它,我成为Leader啦,那么Candidate B就保持Candidate的状态,继续进行选举.

上面我们用三台机器举例,那么如果集群中有四台机器,我们假设它们分别为A,B,C,D,那么很可能有这么一种情况产生,就是A和B都同时获得了两票,谁也不能成为Leader.在Raft中,通过每一台Candidate随机选择一个选举超时时间来避免这个问题.这个选举超时时间应该满足下面这个公式:

broadcastTime << electionTimeout << MTBF

在上面的那个公式中,broadcastTime代表的是机器给其他机器发送RPC的平均时间,包括去和回.MTBF代表机器故障的平均时间.这两个变量都是集群中的一些属性,我们能够选择的只能是electionTimeout.取决于所采用的存储技术,broadcastTime可能在0.5ms~20ms,因此,electionTimeout就可能是10ms~500ms.

日志复制

我们知道,刚被选举出来的Leader的Log Entry,一定是最新的,最全的.为了保持全部的节点上的状态机状态一致,我们需要让它们执行相同的命令序列,所以,我们需要将Leader节点上的Log Entry复制到Follower节点上.

既然要将Leader节点的Log Entry复制到Follower节点上,自然存在Follower节点上的Log Entry和Leader节点上的Log Entry冲突的问题.比如,Follower节点上的Log Entry队列为:[{1, a <- 1}, {1, a <- 2}, {2, a <- 1}],而Leader节点上的Log Entry队列为:[{1, a <- 1}, {1, a <- 2}, {2, a <- 3}].我们在这里用{term, command}来定义一个Log Entry,用[{term, command}, ...]来定义一个Log Entry队列.

我们可以从上面那个例子中看到,Leader节点的Log Entries和Follower节点的Log Entries冲突了.

那么Raft是解决的呢?

我们从前面的部分,也知道了,Leader节点维护着nextIndex[]matchIndex[]这两个变量.我们也知道, nextIndex[]代表的是下次要从那个index之后的Log Entry发送给Follower, matchIndex[]代表Follower和Leader的匹配的最后一个Log EntryIndex.当选举出来一个Leader时,会将nextIndex[]初始化为它的最后一个Log Entry + 1,将matchIndex[]全部初始化为0.

这样,当选举完Leader之后,Leader会把它的最新的一条Log Entry发送给Follower,如果这条Log Entry不匹配,那么Follower会报告一个错误信息,然后Follower节点就依次减少nextIndex[]的值,并再次进行尝试.直到Follower告诉Leader节点,诶,现在你跟我匹配了.然后Follower节点清除那些不匹配的Log Entry并替换成Leader发送过来的Log Entry.

那会不会出现清除已经提交的Log Entry的情况呢?

我个人认为是没有这个可能的.因为在投票决定Leader的时候,选择的就是具有最新的Log Entry的节点.

集群中增加或者删除新成员

这一部分,在实践中应该是必不可少的一部分.但是,说实话,论文中的这部分我是真没看懂,所以请各位还是读原文来获取这部分信息.

快照

随着时间的增加,Log Entries队列可能会越来越大.会占用大量的磁盘空间,所以,我们还需要对Log Entries队列进行压缩,以快照的形式.

Follower可以自行制作快照,但是必要的时候,Leader还是需要给Follower发送快照.一般发生在Follower处理速度慢,或者新节点加入的情况下.

一旦Follower发现Leader发来的快照中,跟它的发生冲突,那么它会放弃它的快照,而转而使用Leader的.

Raft算法的弊端

各位朋友应该已经注意到了,我们上面的讨论,都是基于Fail-Stop Failure来讨论的,并没有涉及到Byzantine Failure.

实际上,Raft算法的实现,就是假设不会发生Byzantine Failure.当然,其他人也提出了一些基于Raft算法的改进版,能够容忍Byzantine Failure.

参考链接

Fault Tolerant Services

What is Raft not good for

分布式系统基础-State Machine

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容