在分布式系统中,保持集群中多个节点在状态上保持一致很重要。但是在实际的现实场景中,节点之间一致性却很难保证。常见的一致性协议有Paxos和Raft,实际生产中只要保证在集群中大部分节点(超过半数以上)可用的情况下,依然能工作并返回一个正确结果即可,从而保证依赖于该集群的其他服务不受影响。
Raft算法是一种基于管理日志复制的一致性算法,功能与Paxos类似,但是相对来说,Raft协议更易于理解,更容易用于实际的系统。
Raft部分概念
Raft协议是Leader-Follower模式,即一个Leader节点和多个Follower节点的模式。每个节点都会维护一个状态机,该状态机一共有3中状态,分别为Leader状态、Follower状态和Candidate状态,任意一个节点都处于这三个状态之一。
状态列表:
Leader: 负责处理所有客户端请求,当接收到写入请求时,本地处理后再同步至其他节点。
Follower: 不发送任何请求,只是响应来自Leader和Candidate的请求。也不会处理Client的请求,而是将请求重定向到Leader节点进行处理。
Candidate: 当Follower节点长时间没有收到Leader节点发送的心跳时,则该节点的选举计时器会过期,同时自身状态会转变为Candidate,并发起新一轮的选举。
其他概念:
选举超时时间(election timeout): 每个Follower节点接收不到Leader节点心跳消息,并不会立即发起新一轮选举,而是选举超时时间过期后切换为Candidate状态,再发起新一轮选举。这主要是因为Leader节点发送的心跳消息可能因为网络延迟或者程序卡顿而迟到或者丢失。这个值一般设置为150ms-300ms之间的随机数。
心跳超时时间(heartbeat timeout): 这个时间是指Leader节点向集群其他Follower节点发送心跳消息的时间间隔。
任期(Term): 任期实际上是一个全局的、连续递增的整数,在Raft协议中每进行一次选举,任期数就会加1,在每个节点中都会记录当前的任期值。每个任期都是从一次选举开始的,在选举时会出现一个或者多个Candidate节点尝试成为Leader节点,如果其中一个Candidate节点赢得选举,该节点就会切换为Leader状态并成为该任期的Leader节点,直到该任期结束。
Raft选举流程
当集群初始化时,多有的节点都是出于Follower状态,此时集群没有Leader节点。
当处于Follower状态的节点在一段时间(选举计时器超时时间)内未收到Leader节点的心跳信息,就会认为节点出现故障导致其任期(Term)过期,节点就会转换为Candidate状态,重置选举计时器并发起新一轮选举。
选举时发起选举的节点首先会将选票投给自己,并会向集群中其他节点发送选举请求。其它节点任期较小且都是Follower状态,所以节点选举请求后,就会将选票投出,重置选举计时器,并递增自身Term值。这样因为之前的Cadidate节点得到了集群中超过半数的票数,所以就变成了Leader节点。
但是这里就有个问题,如果两个或两个以上的节点选举计时器同时过期,那么这些节点都会切换为Candidate状态,同时出发新一轮选举,在选举中每个Candidate都无法获得半数以上的票数,这样就没办法选举出Leader节点了。其实Raft的解决办法是选举失败,由于选举超时时间是个区间内的随机数,所以会等待其中某个选举计时器过期,重新发起选举。只要Term最高的节点获得了半数以上的票数,Leader节点就产生了。
日志复制
- Leader节点需要处理Client的请求,并将Client的更新操作以消息(Append Entries消息)的形式发送到集群中其他的Follower节点。
- 当Follower节点记录收到消息后,会向Leader节点返回相应的响应消息。
- 当Leader节点在收到半数以上Follower节点的响应消息后,会对Client的请求进行应答。
- 最后Leader会提交Client的更新操作,该过程会发送Append Entries消息到Follower节点,通知Follower节点该操作已经提交,同时Leader节点和Follower节点也就可以将该操作应用到自己的状态机中。
实际的流程图如下: