赞
踩
目的 :一致性算法的出现是为了解决一致性问题,一致性问题是指对于一组服务器(集群),给定一组操作,需要使用一种协议使得它们的结果最终达成一致,看起来好像是一台服务器一样。
作用 :一致性算法在构建可信赖的大规模软件中扮演者重要的角色,常用的一致性算法Raft、Paxos算法等。
提出背景:
一致性算法是在复制状态机的背景下产生的,复制状态机用于解决分布式系统中的各种容错问题。复制状态机通过使用复制日志实现
复制状态机通过复制日志实现:
应用于实际系统的一致性算法一般有以下特性:
raft算法是一种比Paxos更加易于学习和理解的一致性算法。为了提升raft算法的可理解性,采用了算法分解(raft主要被分为领导人选举、日志复制、安全和成员变化 )和减少需要考虑的状态的数量将状态空间简化两种方式
在所有服务器上持久存在的:(在响应远程过程调用RPC之前稳定存储的)
名称 | 描述 |
---|---|
currentTerm | 服务器最后知道的任期号(从0开始递增) |
votedFor | 在当前任期内收到选票的候选人id(如果没有就为null) |
log[] | 日志条目;每个条目包含状态机的要执行的命令和从领导人处收到时的任期号 |
在所有的服务器上不稳定存在的:
名称 | 描述 |
---|---|
commitIndex | 已知的被提交的最大日志条目的索引值(从0开始递增) |
lastApplied | 被状态机执行的最大日志条目的索引值(从0开始递增) |
在领导人服务器上不稳定存在的:(在选举之后初始化)
名称 | 描述 |
---|---|
nextIndex[] | 对于每一个服务器,记录需要发给它的下一个日志条目的索引(初始化为领导人上一条日志的索引值+1) |
matchIndex[] | 对于每一个服务器,记录已经复制到该服务器的日志的最高索引值(从0开始递增) |
性质 | 描述 |
---|---|
选举安全原则(Election Safety) | 一个任期(term)内最多允许有一个领导人被选上 |
领导人只增加原则(Leader Append-Only) | 领导人永远不会覆盖或删除自己的日志,它只会增加 |
日志匹配原则(Log Matching) | 如果两个日志在相同的索引位置上的日志条目的任期号相同,那么我们就认为这个日志从头到这个索引位置之间的条目完全相同 |
领导人完全原则(Leader Completeness) | 如果一个日志条目在一个给定任期内被提交,那么这个条目一定会出现在所有任期号更大的领导人中 |
状态机安全原则(State Machine Safety) | 如果一个服务器已经在给定索引位置的日志条目中应用状态机,则所有其他服务器不会在该索引位置应用不同的条目 |
raft三个相对独立子问题介绍:
raft集群中一台机器在三种状态间流转,包括领导人(Leader)、候选人(Candidate)、追随者(Follower)
+ 追随者只响应其他服务器的请求。
+ 如果追随者没有收到任何消息,它会成为一个候选人并且开始一次选举。
+ 收到大多数服务器投票的候选人会成为新的领导人。
+ 领导人在它们宕机之前会一直保持领导人的状态。
raft算法将时间划分成为任意不同长度的任期(term)。任期用连续递增的数据表示。
任期在raft中充当逻辑时钟的角色,可用来检查过期的信息,比如领导人。
注 第三个任期内选票被瓜分,没有领导人。
raft在设计的时候就要求安全不依赖于时序(timing)。领导选举是raft中对时序要求最关键的地方。raft会选出一个稳定领导人的前提是系统满足以下时序要求:
brodcastTime << electionTimeout << MTBF
MTBF:指单个服务器发生故障的间隔的平均时间。
注意:broadcastTime应该比electionTimeout小一个数量级,为的是使领导人能够持续发送心跳信息来阻止追随者们开始选举。electionTimeout要比MTBF小几个数据级,为的是使系统能够稳定运行。
Raft 的 RPC 需要接收方将信息持久化的保存到稳定存储中去,所以广播时间大约是 0.5 毫秒到 20 毫秒,这取决于存储的技术。因此,electionTimeout
一般在 10毫秒到500毫秒之间。大多数的服务器的MTBF
都在几个月甚至更长,很容易满足这个时序需求。
raft中的服务器通过远程过程调用(RPC)来通信,基本的raft一致性算法仅需要2种RPC。
注意:加入第三种RPC来在各个服务器之间传输快照(snapshot),领导者使用该RPC来发送快照给太落后的追随者。
raft使用一种心跳机制来触发领导人选举。当服务器程序启动时,他们都是跟随者。一个服务器节点只要能从领导人或候选人处收到有效的RPC就一直保持跟随者状态。领导人会向所有追随者周期性发送心跳(heartbeat,不带任何日志条目的AppendEntriesRPC)来保持领导人的地位。如果一个追随者在一个周期内没有收到心跳信息,就叫做选举超时(election timeout),然后它就会假定没有可用的领导人并且开始一次选举来选出一个新的领导人。
在开始一次选举过程,跟随者先增加自己当前任期号并转换为候选人状态,然后投票给自己并且并行地向集群中其他服务器节点发送RequestVote RPC,候选人会一直处于该状态,直到下列三种情况之一发生:
注意:
a)前两种情况很好理解,对于同一个任期,每个服务器节点只会投给一个候选人,按照先来先服务的原则。一旦候选人赢得选举,就立即成为leader。然后它会向其他的服务器节点发送心跳消息来确定自己的地位并阻止新的选举。
b)raft算法使用随机选举超时时间来确保很少发生选票瓜分的情况,选举超时时间是一个固定的区间,在大多数情况下只有一个服务器会选举超时,然后该服务器赢得选举并在其他服务器超时之前发送心跳。(上图展示了选票一开始被瓜分的情况)
一旦选出了领导人,它就开始接收客户端的请求。
客户端的每一个请求都包含一条将要被复制状态机执行的指令。
Leader把客户端请求的指令作为一个新的条目追加到日志中去,然后并行的发起AppendEntries RPC 给其他的服务器,让它们复制该条目。
当该条目是已提交的 ,leader会应用该条目到它的状态机(状态机执行该指令) ,然后把执行结果返回给客户端。
注意: 如果跟随者崩溃或者运行缓慢,或者网络丢包,领导人会不断的重试AppendEntriesRPC(j即使已经回复了客户端)直到所有的跟随者最终存储了所有的日志条目。
日志由上图所示的方式组织。每个日志条目存储一条状态机指令 和领导人收到该指令时的任期号 (任期号用于检测多个日志副本之间不一致的情况)。为了保证日志匹配的原则,每个条目都有一个整数索引来表示它在日志中的位置。
一旦领导人创建的条目已经复制到了大多数的服务器上,这个条目被称为可被提交的(commited,上图中的7号条目) ,
设计raft日志机制来保证不同服务器上日志的一致性。raft保证了以下两个特性:
如果在不同日志中的两个日志条目有着相同的索引号和任期号,则它们所存储的命令是相同的。
原因: 领导人在一个任期里在一个任期里在给定的日志索引位置最多创建条日志条目,同时该条目在日志中的位置也不会改变。
如果在不同日志中的两个条目条目有着相同的索引和任期号,则它们之前的所有条目都是完全一样的。
原因: 这个特性源于AppendEntries的一个简单的一致性检查。当发送一个AppendEntries RPC时,领导人会把新的日志条目紧接着的之前的条目的索引位置和任期号都包含在里面 。如果追随者没有在它的日志中找到相同索引和任期号的日志,它就会拒绝新的日志条目。这个一致性检查就像一个归纳的步骤,只要AppendEntries返回成功的时候,领导人就知道追随者们的日志和它的是一致的。
领导者的崩溃会导致日志的不一致,包括以下三种情况:
一个追随者可能丢失掉领导人上的一些条目(a、b)
注意:
1.在raft算法中,领导人通过强制追随者们复制它的日志来处理日志的不一致。这就意味着,在追随者上的冲突日志会被领导者的日志覆盖掉。
nextIndex
,它表示领导人将要发送给该追随者的下一条日志条目的索引。当一个领导人开始掌权时,它会将nextIndex
初始化为它的最新的日志条目索引数+1(上图的 11)。nextIndex
递减然后重试 AppendEntries RPC。最终nextIndex
会达到一个领导人和追随者日志一致的地方。2.一个领导人从来不会覆盖或者删除自己的日志。
3.在通常情况下,一条新的日志条目可以在一轮新的日志条目可以在一轮RPC内完成在集群内大多数服务器上的复制。一个速度很慢的追随者并不会影响整体的性能。
前面介绍了raft算法是如何进行领导选举和复制日志的,但到现在为止并不能保证每一个状态机能按照相同的顺序执行同样的指令。下面介绍选举限制 来保证该特性。
raft使用一种简单的方式来保证新的领导人在开始选举的时候在之前任期的所有已提交的日志条目都会出现在上边,这就意味着日志条目只有一个流向:从领导人流向追随者。领导人永远不会覆盖已经存在的日志条目。
raft使用投票的方式来阻止没有包含全部日志条目的服务器赢得选举。一个候选人为了赢得选举必须和集群中的大多数进行通信,这就意味着每一条已经提交的日志条目最少在其中一台服务器上出现。如果候选人的日志至少和大多数服务器上的日志一样新,那么它一定包含有全部的已经提交的日志条目。
注意:raft 通过比较日志中最后一个条目的索引和任期号来决定两个日志哪一个更新。如果两个日志的任期号不同,任期号大的更新;如果任期号相同,更长的日志更新。
如图的时间序列解释为什么领导人不能通过之前任期的日志条目判断他的提交转态?
时间序列 | 描述 |
---|---|
a | (a)中的 S1 是领导人并且部分复制了索引2上的日志条目。 |
b | (b)中 S1 崩溃了;S5 通过 S3,S4 和自己的选票赢得了选举,并且在索引2上接收了另一条日志条目。 |
c | (c)中 S5 崩溃了,S1 重启了,通过 S2,S3 和自己的选票赢得了选举,并且继续索引2处的复制,这时任期2的日志条目已经在大部分服务器上完成了复制,但是还并没有提交。 |
d | 如果(d)时刻 S1 崩溃了,S5 会通过 S2,S3,S4 的选票成为领导人,然后用它自己在任期3的日志条目覆盖掉其他服务器的日志条目。 |
e | 如果在崩溃之前,S1 在它的当前任期在大多数服务器上复制了一条日志条目,就像在(e)中那样,那么这条条目就会被提交(S5就不会赢得选举)。在这时,之前的日志条目就会正常被提交。 |
上图描述了一条存储在了大多数服务器上的日志条目仍然被新上任的领导人覆盖了,为了消除该问题,raft从来不会通过计算复制的数目来提交之前任期的日志条目。只有领导人当前任期的日志条目才能通过计算数目来提交。 一旦当前任期的日志条目以这种方式被提交,那么由于日志匹配的原则(Log Matching Property),之前日志条目也都会被间接的提交。
论文中这段内容比较难理解,更加直观的解释: 由于raft不会提交之前任期之前的日志条目,那么就不会从b过渡c的情况,步骤c中s1的最新任期为4,所以在提交过程中不会有单独提交2任期的日志条目的情况,所以只能从b发生s5宕机的情况下直接过渡到e,这样就产生更新的任期4,s5中的任期3就没有机会被选成为领导人了。
集群成员变更和成员宕机后重启不同,成员变更会修改成员的个数进而影响领导人的选举。解决方案如下两种:
为了让配置修改机制能够安全,在转换的过程中在同一个任期内不能出现两个领导人。不幸的是,服务器集群从旧的配置直接升级到新的配置的任何方法都是不安全的 ,一次性自动转换所有的服务器是不可能的,所以集群可以在转换的过程中划分成两个单独的组。
为了保证安全性,集群配置的调整必须使用两阶段(two-phase)方法。在raft中,集群先切换到一个过渡配置,我们称其为共同一致性(joint consensus) ,一旦共同一致性被提交 了,然后系统再切换到新的配置。共同一致是旧的配置和新的配置的组合:
共同一致允许独立的服务器在不影响安全性的前提下,在不同的时间进行配置转换过程。此外,共同一致可以让集群在配置转换过程依然能够响应服务器请求。
上图为集群配置变更的时间线。虚线表示的是已经创建但还没提交的配置条目,实线表示的是最新提交的配置条目。领导人首先在它的日志中创建Cold,new配置条目并且将它提交到Cold,new(使用旧配置的大部分服务器和使用新配置的大部分服务器)。然后创建它创建Cnew配置条目并且将它提交到使用新配置的大部分机器上。这样就不存在Cold和Cnew能够分别同时做出决定的时刻。
注意: 一旦 Cold,new 被提交,那么无论是 Cold 还是 Cnew,在没有经过他人批准的情况下都不可能做出决定,并且领导人完全特性(Leader Completeness Property)保证了只有拥有 Cold,new 日志条目的服务器才有可能被选举为领导人。
问题1:开始的时候新的服务器可能没有任何日志条目,此时加入集群中,可能需要一段时间来追赶,同时在这个阶段还不能提交新的日志条目。
答:为了避免这种可用性的时间间隔,raft在配置更新的时候使用了一种额外的阶段,在这个阶段,新的服务器以没有投票权的身份加入到集群中来(领导人复制日志给它们,但是不把它们考虑到大多数中)。
问题2: 集群的领导人可能不是新配置的一员
答:在这种情况下,领导人就会在提交了Cnew日志之后退位(回到跟随者转态)。
问题3:移除不在Cnew中的服务器可能会扰乱集群
答:为了避免这个问题,当服务器确认领导人存在时,服务器会忽略RequestVote RPC。
快照(snapshot)是最简单的压缩方式。在快照中,全部当前系统状态都被写入快照中,存储到持久化存储中,然后在那个时刻之前的全部日志都可以被丢弃。在Chuuby和Zookeeper中都使用了快照技术。增量压缩(incremental approaches)的方法,例如日志清理(log cleaning)或者日志结构合并树(log-structed merge trees) 都是可行的。
和简单操作整个数据集合的快照相比,需要增加复杂的机制来实现。状态机可以使用和快照相同的接口来实现LSM tree,但是日志清除方法就需要修改raft了。
注: 一个服务器用新的快照替换了从1到5的条目,快照值存储了当前的状态。快照中包含了最后的索引位置和任期号。
上图阐述了raft中快照的基础思想。每个服务器独立创建快照 ,只包含已经被提交的日志。raft也将少量的元数据包含到快照中:最后被包含的索引(last inclouded index) 指的是快照被取代的最后的条目在日志中的索引值,最后被包含的任期(last inclouded term) 指的是该条目的任期号。
目的: 为了支持快照后的第一个条目的附加日志请求时的一致性检查。
注意: 尽管通常服务器都是独立的创建快照,当存在一个运行非常缓慢或新加入集群的服务器跟随者,为了让这个跟随者更新到最新的状态就是通过网络把快照发送给它们。
服务器必须决定什么时候应该创建快照。
如果快照创建的过于频繁,就会浪费大量的磁盘带宽和其他资源;如果创建快照频率太低,它就要承受耗尽存储容量的风险,同时增加了日志重建的时间。一个简单的策略就是当日志大小达到一个固定大小的时候就创建一次快照
写入快照需要花费显著的一段时间,并且我们还不希望影响到正常操作。
解决方案是通过写时复制(copy-on-write)的技术,这样新的更新就可以被接收而不影响到快照。
raft解决只读(read-only)的操作引起的返回过期数据(stale data)的问题
笔者水平有限在理解和分析的过程中,难免会有疏漏,欢迎各位指出。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。