概述
目的 :一致性算法的出现是为了解决一致性问题,一致性问题是指对于一组服务器(集群),给定一组操作,需要使用一种协议使得它们的结果最终达成一致,看起来好像是一台服务器一样。
作用 :一致性算法在构建可信赖的大规模软件中扮演者重要的角色,常用的一致性算法Raft、Paxos算法等。
提出背景:
一致性算法是在复制状态机的背景下产生的,复制状态机用于解决分布式系统中的各种容错问题。复制状态机通过使用复制日志实现
复制状态机通过复制日志实现:
- 日志:每台机器保存一份日志,日志来自于客户端的请求,包含一系列的命令。
- 状态机:状态机会按顺序执行这些命令。
- 一致性模型:分布式环境下,保证多机的日志是一致的,这样回放到状态机中的状态时一致的。
应用于实际系统的一致性算法一般有以下特性:
- 确保安全性(从来不会返回一个错误的结果),即使在所有的非拜占庭情况下,包括网络延迟、分区、丢包、冗余和乱序的情况下。
- 高可用性,只要集群中的大部分机器都能运行,可以互相通信并且可以和客户端通信,这个集群就可用。
- 例如一个拥有5台机器的集群可以容忍其中2台机器失败(fail)。
- 不依赖时序保证一致性,时钟错误和极端情况下的消息延迟在最坏的情况下才会引起可用性问题。
- 通常情况下,一条命令能够尽可能快的在大多数节点对外一轮远程调用作出响应时完成,一少部分慢的机器不会影响系统的整体性能。
raft算法组成
raft算法是一种比Paxos更加易于学习和理解的一致性算法。为了提升raft算法的可理解性,采用了算法分解(raft主要被分为领导人选举、日志复制、安全和成员变化 )和减少需要考虑的状态的数量将状态空间简化两种方式
raft算法新的特性
- 强领导者: 与其他一致性算法相比,raft使用一种更强领导力形式。
- 日志条目只能从领导者发送给其他服务器
- 领导选举 :raft使用一个随机计时器来选举领导者
- 目的是为了解决冲突,为了避免1个term(选举周期)过去,还是没有选举结果的情况,raft协议在设计的时候,就将每个server的选举时间(election time)随机的设置为不同的值,所以第一个选举timeout的server就可以先发起下一次的选举。
- 成员变更: 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三个相对独立子问题介绍:
- 领导人选取(Leader election):在一个领导人宕机之后必须要选取一个新的领导人。
- 日志复制(Log replication):领导人必须从客户端接收日志然后复制到集群中的其他服务器,并且强制要求其他的服务器的日志和自己相同。
- 安全性(Safety):raft的关键的安全特性是转态机安全(State Machine Safety)原则 ,一个服务器已经将给定索引位置的日志条目应用到状态机中,则所有其他服务器不会在该索引位置应用不同的条目。
raft基础
服务器状态
raft集群中一台机器在三种状态间流转,包括领导人(Leader)、候选人(Candidate)、追随者(Follower)
+ 追随者只响应其他服务器的请求。
+ 如果追随者没有收到任何消息,它会成为一个候选人并且开始一次选举。
+ 收到大多数服务器投票的候选人会成为新的领导人。
+ 领导人在它们宕机之前会一直保持领导人的状态。
任期
raft算法将时间划分成为任意不同长度的任期(term)。任期用连续递增的数据表示。
- raft算法保证在给定的一个任期最少 要有一个领导人,选票被瓜分则该任期无领导人。
任期在raft中充当逻辑时钟的角色,可用来检查过期的信息,比如领导人。
- 服务器间进行通信是,会互相交互任期号,如果领导人的任期号比其他服务器小,则将自己转变为跟随者;如果一台服务器收到的任期号是过时的,则会拒绝此次请求
注 第三个任期内选票被瓜分,没有领导人。
时序
raft在设计的时候就要求安全不依赖于时序(timing)。领导选举是raft中对时序要求最关键的地方。raft会选出一个稳定领导人的前提是系统满足以下时序要求:
brodcastTime << electionTimeout << MTBF
- brodcastTime:指一台服务器并行的向集群中的其他服务器发送RPC并且收到的响应的平均时间。
- electionTimeout:指选举超时时间。
MTBF:指单个服务器发生故障的间隔的平均时间。
注意:broadcastTime应该比electionTimeout小一个数量级,为的是使领导人能够持续发送心跳信息来阻止追随者们开始选举。electionTimeout要比MTBF小几个数据级,为的是使系统能够稳定运行。
Raft 的 RPC 需要接收方将信息持久化的保存到稳定存储中去,所以广播时间大约是 0.5 毫秒到 20 毫秒,这取决于存储的技术。因此,electionTimeout
一般在 10毫秒到500毫秒之间。大多数的服务器的MTBF
都在几个月甚至更长,很容易满足这个时序需求。
基本的raft
raft中的服务器通过远程过程调用(RPC)来通信,基本的raft一致性算法仅需要2种RPC。
- RequestVoteRPC是候选人在选举过程中触发的。
- AppendEntriesRPC是领导人触发的,为的是复制日志条目和提供一种心跳机制。
注意:加入第三种RPC来在各个服务器之间传输快照(snapshot),领导者使用该RPC来发送快照给太落后的追随者。
领导人选举
raft使用一种心跳机制来触发领导人选举。当服务器程序启动时,他们都是跟随者。一个服务器节点只要能从领导人或候选人处收到有效的RPC就一直保持跟随者状态。领导人会向所有追随者周期性发送心跳(heartbeat,不带任何日志条目的AppendEntriesRPC)来保持领导人的地位。如果一个追随者在一个周期内没有收到心跳信息,就叫做选举超时(election timeout),然后它就会假定没有可用的领导人并且开始一次选举来选出一个新的领导人。
在开始一次选举过程,跟随者先增加自己当前任期号并转换为候选人状态,然后投票给自己并且并行地向集群中其他服务器节点发送RequestVote RPC,候选人会一直处于该状态,直到下列三种情况之一发生:
- 它自己赢得了这次的选举(收到过半的投票)
- 其他的服务器节点成为leader
- 一段时间之后没有任何候选人获胜,选票被瓜分该任期内无领导人,继续下一个任期的选举
注意:
a)前两种情况很好理解,对于同一个任期,每个服务器节点只会投给一个候选人,按照先来先服务的原则。一旦候选人赢得选举,就立即成为leader。然后它会向其他的服务器节点发送心跳消息来确定自己的地位并阻止新的选举。
b)raft算法使用随机选举超时时间来确保很少发生选票瓜分的情况,选举超时时间是一个固定的区间,在大多数情况下只有一个服务器会选举超时,然后该服务器赢得选举并在其他服务器超时之前发送心跳。(上图展示了选票一开始被瓜分的情况)
日志复制
一旦选出了领导人,它就开始接收客户端的请求。
客户端的每一个请求都包含一条将要被复制状态机执行的指令。
Leader把客户端请求的指令作为一个新的条目追加到日志中去,然后并行的发起AppendEntries RPC 给其他的服务器,让它们复制该条目。
当该条目是已提交的 ,leader会应用该条目到它的状态机(状态机执行该指令) ,然后把执行结果返回给客户端。
注意: 如果跟随者崩溃或者运行缓慢,或者网络丢包,领导人会不断的重试AppendEntriesRPC(j即使已经回复了客户端)直到所有的跟随者最终存储了所有的日志条目。
日志由上图所示的方式组织。每个日志条目存储一条状态机指令 和领导人收到该指令时的任期号 (任期号用于检测多个日志副本之间不一致的情况)。为了保证日志匹配的原则,每个条目都有一个整数索引来表示它在日志中的位置。
一旦领导人创建的条目已经复制到了大多数的服务器上,这个条目被称为可被提交的(commited,上图中的7号条目) ,
- raft保证可被提交的日志条目是持久化的并且最终会被所有可用的状态机执行。
- 领导人日志中之前的条目都是可被提交的(commited),包括由之前的领导人创建的条目。
- 一旦一个追随者知道了一个日志条目已经被提交,它将该条目应用至本地的状态机。
设计raft日志机制来保证不同服务器上日志的一致性。raft保证了以下两个特性:
如果在不同日志中的两个日志条目有着相同的索引号和任期号,则它们所存储的命令是相同的。
原因: 领导人在一个任期里在一个任期里在给定的日志索引位置最多创建条日志条目,同时该条目在日志中的位置也不会改变。
如果在不同日志中的两个条目条目有着相同的索引和任期号,则它们之前的所有条目都是完全一样的。
原因: 这个特性源于AppendEntries的一个简单的一致性检查。当发送一个AppendEntries RPC时,领导人会把新的日志条目紧接着的之前的条目的索引位置和任期号都包含在里面 。如果追随者没有在它的日志中找到相同索引和任期号的日志,它就会拒绝新的日志条目。这个一致性检查就像一个归纳的步骤,只要AppendEntries返回成功的时候,领导人就知道追随者们的日志和它的是一致的。
领导者的崩溃会导致日志的不一致,包括以下三种情况:一个追随者可能丢失掉领导人上的一些条目(a、b)
- 一个追随者可能包含一些领导人未提交的日志条目(c、d)
- 一个追随者可能即丢失掉领导人上的一些条目也包含一些领导人未提交的日志条目
注意:
1.在raft算法中,领导人通过强制追随者们复制它的日志来处理日志的不一致。这就意味着,在追随者上的冲突日志会被领导者的日志覆盖掉。
- 每一个追随者维护了一个
nextIndex
,它表示领导人将要发送给该追随者的下一条日志条目的索引。当一个领导人开始掌权时,它会将nextIndex
初始化为它的最新的日志条目索引数+1(上图的 11)。 - 如果一个追随者的日志和领导者的不一致,AppendEntries 一致性检查会在下一次 AppendEntries RPC 时返回失败。在失败之后,领导人会将
nextIndex
递减然后重试 AppendEntries RPC。最终nextIndex
会达到一个领导人和追随者日志一致的地方。 - 此时,AppendEntries 会返回成功,追随者中冲突的日志条目都被移除了,并且添加所缺少的上了领导人的日志条目。
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实现线性化语义(linearizable semantics 每一次操作立即执行,在它调用和收到回复之间只执行一次)
- 解决方案就是客户端对于每一条指令都赋予一个唯一的序列号。然后,状态机跟踪每条指令最新的序列号和相应的响应。如果接收到一条指令,它的序列号被执行,那么就立即返回结果,而不重新执行指令。
raft解决只读(read-only)的操作引起的返回过期数据(stale data)的问题
- 领导人在处理只读的请求之前必须检查自己是否已经被废除了(如果一个更新的领导人被选举出来,它的信息就已经过期了)。raft中通过让领导人在响应只读请求之前,先和集群中的大多数节点交换一次(heartbeat)信息来处理这个问题。
参考资料
Raft一致性算法论文译文
- Raft一致性算法笔记
笔者水平有限在理解和分析的过程中,难免会有疏漏,欢迎各位指出。
最后
以上就是神勇月亮为你收集整理的一致性算法分析的全部内容,希望文章能够帮你解决一致性算法分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复