Raft算法
1.1 共识算法
共识是可容错系统中的一个基本问题:即使面对故障,服务器也可以在共享状态上达成一致。
共识算法允许一组节点像一个整体一样一起工作,即使其中的一些节点出现故障也能够继续工作下去,其正确性主要是缘于复制状态机的性质:一组Server的转发计计算相同状态的副本,即使有一部分Server宕机它们也能运行。
(状态机介绍:这里转载pingxiaozhao作者的博客,地址如下:(151条消息) 什么是状态机?_pingxiaozhao的博客-CSDN博客_状态机的概念)
一般通过复制日志来实现复制状态机。每个Server存储这一份包括命令序列的日志文件,状态机会按照顺序执行这些命令。因为每个日志包含相同的命令,并且顺序也想通,所以每个状态机处理相同的命令序列。由于状态机是确定性的,所以处理相同的状态,得出相同的输出。
因此共识算法的工作就是保持复制日志的一致性。服务器上的共识模块从客户端接收命令并将它们添加到日志中。它与其他服务器上的共识模块通信,以确保及时某些服务器发生故障,每个日志最终包含相同顺序的请求。一旦命令被正确地复制,它们就称为已提交。每个服务器的状态机按照日志顺序处理已提交的命令,并将输出返回给客户端,因此,这些服务器形成了单一的、高度可靠的状态机。
适用于实际系统的共识算法通常具有以下特性:
安全:网络延迟、分区、包丢失、赋值和重新排序、
高可用:只要大多数服务器都是可操作性的,并且可以相互通信,也可以与客户端进行通信,那么这些服务器就可以看做完全功能可用的。因此,一个典型的由舞台服务器组成的集群可以容忍任何两台服务器端故障。假设服务器因停止而发生故障;它们稍后可能会从稳定存储上的状态中恢复并重新加入集群。
一致性不依赖时序:错误的始终和极端的消息延迟,在最坏的情况下也只会造成可用性问题,而不会产生一致性问题。
在集群中大多数服务器响应,命令就可以完成, 不会被少数运行缓慢的服务器来影响整体系统性能。
2 基础
2.1 节点类型
一个Raft集群包括若干服务器,以典型的5服务器举例。在任意的时间,每个服务器一定会处于以下三个状态中的一个:
- Leader:负责发起心跳,响应客户端,创建日志,同步日志。
- Candidate(候选人):Leader选举过程中的临时角色,由Follower转化而来,发起投票参与竞选。
- Follower:接收Leader的心跳和日志同步数据,投票给Candidate。
2.2 任期
如上图,raft算法将时间划分为任意长度的任期,任期用连续的数字表示,看作当前term号。每一个任期的开始都是一次选举,在选举开始时,一个或多个Candidate都会尝试称为Leader。如果Candidate赢得了选举,它就会在该任期内担任Leader。如果没有选出Leader,将会开启另一个任期,并立刻开始下一次选举。raft算法保证在给定的一个任期要有一个Leader。
每个节点都会存储当前的term号,当服务器之间进行通信时会交换当前term号;如果服务器发现自己的term好比其他人小,那么他会更新到较大的term值。如果一个Candidate或者Leader发现自己的term过期了,他会立即退回程Follower。如果一台服务器收到的请求的term号是过期的,那么它会拒绝此次请求。
2.3 日志
entry:每一个是事件都会成为entry,只有Leader可以创建entry。entry的内容为<term,index,cmd>其中cmd是可以应用到状态机的操作。
log:由entry构成的数组,每一个entry总是先被Leader添加到自己的log数组中,然后再发起共识请求,获得同意后才会被Leader提交给状态机。Follower只能从Leader获取新日志和当前的commitIndex,然后把对应的entry应用到自己的状态机。
3 领导人选举
raft使用心跳机制来触发Leader的选举。
如果一台服务器能够收到来自Leader或者Candidate的有效信息,那么它会一直保持为Follower状态,并且刷新自己的electionElapsed,重新计时。
Leader会向所有的Follower周期性发送心跳来保证自己的Leader地位。如果一个Follower在一个周期内没有收到心跳信息,这就叫做选举超时,然后它就会认为此时没有可用的Leader,并且开始进行一次选举以选出一个新的Leader。
为了开始新的选举,Follower会自增的term号并且转换状态为Candidate。然后他会向所有节点发起RequestVoteRPC请求,Candidate的状态会持续到以下情况发生:
- 赢得选举
- 其他节点赢得选举
赢得选举的条件是:一个Candidate在一个任期内收到了来自集群中的多数选票(N+1/2),就可以成为Leader。
在Candidate等待选票的时候,它可能受到其他节点声明自己是Leader的心跳,此时有两种情况:
- 该Leader的term号大于等于自己的term号,说明对方已经成为Leader,则自己回退到Follower。
- 该Leader的term号小于自己的term号,那么会拒绝该请求并让该节点更新term。
由于同一时刻出现多个Candidate,导致没有Candidate获得大多数选票,如果没有其他手段来重新分配选票的话,那么会无限重复下去。
raft使用了随机的选举超时时间来避免上述情况。每个Candidate在发起选举后,都会随机化一个新的枚举超时时间,这种机制使得各个服务器能够分散开来,在大多数情况下只有一个服务器会率先超时;它会在其他服务器超时之前赢的选举。
4 日志复制
一旦选出了Leader,它就开始接收客户端的请求。每一个客户端的请求都包含一条需要被复制状态机执行的命令。
Leader收到客户端请求后,会生成一个entry,包含<index,term,cmd>,再将这个entry添加到自己的日志末尾后,向所有的节点广播该entry,要求其他服务器赋值这条entry。
如果Follower接收该entry,则会entry添加到自己的日志后面,同时返回给Leader同意。
如果Leader收到了多数的成功响应,Leader会将这个entry应用到自己的状态机中,之后可以成为这个entry是committed,并且向客户端返回执行结果。
raft保证以下两个性质
- 在两个日志里,有两个entry拥有相同的index和term,那么它们一定有相同的cmd
- 在两个日志里,有两个entry拥有相同的index和term,那么它们前面的entry也一定相同
!通过仅有Leader可以entry来保证第一个性质,第二个性质需要一致性检查来进行保证。
一般情况下,Leader和Follower的日志保持一致,然后,Leader的崩溃会导致日志不一样,这样一致性检查会产生失败。Leader通过Follower复制自己的日志来处理日志的不一样。这就意味着,在Follower上冲突日志会被领导者的日志覆盖。
为了使得Follower的日志和自己的日志一致,Leader需要找到Follower与它日志一致的地方,然后删除Follower在该位置之后的日志,接着把这之后的日志发送给Follower。
Leader给每一个Follower维护一个nextIndex,它表示Leader将要发送给追随者的下一条日志条目的索引。当一个Leader开始掌权时,它会将nextIndex初始化为它的最新的日志条目索引数+1.如果一个Follower的日志和Leader的不一致,AppendEntries一致性检查会在下一次RPC返回失败。在失败之后,Leader会将nextIndex递减然后重试RPC。最终nextIndex会达到一个Leader和Follower日志一致的地方。这时,AppendEntries会返回成功,Follower中冲突的日志条目都被移除,并且添加所缺少的上了Leader的日志条目。一旦AppendEntries返回成功,Follower和Leader的日志就一致了,这样的状态会保持到任期结束。
5 安全性
5.1 选举限制
Leader需要保证自己存储全部已经提交的日志条目。这样才可以使日志条目只有一个流向:从Leader流向Follower,Leader永远不会覆盖已经存在的日志条目。
每个候选者发送RPC时候,都会带上最后一个entry信息。所有节点收到投票信息时,会对该entry进行比较,如果发现自己的更新,拒绝投票给该Candidate。
判断日志新旧的方式:如果两个日志的term不同,term大则更;如果term相同,更长的index更新。
5.2 节点崩溃
如果Leader崩溃,及群众的节点在electionTimeout时间内没有收到Leader的心跳信息就会触发新一轮选举,在选举期间整个集群对外不可用。
如果Follower和Candidate崩溃,处理方式会简单很多。之后发给它的请求会失败。由于raft所有请求都是幂等的,失败会重试,崩溃恢复后,可以接收新请求
5.3 时间与可用性
raft的要求之一就是安全性不依赖于时间:系统不能仅仅因为一些时间发生的比预想的快一些或者慢一些就产生错误。
broadcastTime << electionTimeout << MTBF
broadcastTime :向其他节点并发发送消息的平均响应时间;
electionTimeout:选举超时时间;
MTBF:单台及其的平均健康时间;
broadcastTime 比 electionTimeout小一个数量级,为了使Leader能持续发送心跳信息来阻止Follower的选举
electionTimeout比MTBF小几个数量级,为了系统稳定运行。当Leader崩溃时,大约整个electionTimeout的时间内不可用;