简介
Raft 协议是分布式领域解决一致性的又一著名协议,主要包含 leader 选举和日志复制部分两部分。
leader 选举
节点状态
Raft 协议中节点有三种状态(角色)
- Follower 跟随者
- Candidate 候选者
- Leader 领导者,也就是主节点
单节点发起的投票
最初节点的状态为 Follower,每个节点会有一个计时器,其时间设置为 100~300ms 之间的随机值。当计时器到期后,节点状态从 Follower 变成 Candidate。率先变更状态的节点将发起一轮投票,设置本轮轮次为 1,然后为自己投上一票。接下来向其它节点发起投票请求。
集群内的其他节点如果本轮未投票,则投给该节点,并将结果返回,重置计时器。当某个节点收到的赞成票超过一半时,变更自己的状态为 Leader,然后向集群内的其他节点发送心跳信息。
其他节点在收到 Leader 的心跳包后,返回响应结果,并重置自身的计时器。当 Follower 节点一段时间没有收到 leader 的心跳,则会改变自己的状态为 Candidate,并发起新一轮投票。
多节点发起的投票
基本流程和单节点相同。当本轮没有 leader 被选出时,当前轮次就失效了。Candidate 和 follower 都会在投票后启动一个定时器,在定时器结束时没有 leader 被选出,则会自动发起下一轮投票。
在投票时还有一个保障一致性的机制。就是 candidate 在发送投票请求时,会附带上自己的 term 和 index。在其它节点收到请求后,如果发现该 term 和 index 小于自己的,就会拒绝投票。这样就保证了选出来的新 leader 会拥有大部分日志。
日志复制
当Leader被选出来后,就可以接受客户端发来的请求了,每个请求包含一条需要被replicated state machines执行的命令。leader会把它作为一个log entry append到日志中,然后给其它的server发AppendEntriesRPC请求。当Leader确定一个log entry被safely replicated了(大多数副本已经将该命令写入日志当中),就apply这条log entry到状态机中然后返回结果给客户端。如果某个Follower宕机了或者运行的很慢,或者网络丢包了,则会一直给这个Follower发AppendEntriesRPC直到日志一致。
当一条日志是commited时,Leader才可以将它应用到状态机中。Raft保证一条commited的log entry已经持久化了并且会被所有的节点执行。
保证一致性机制
当新 leader 被选出时,leader会为每个follower维护一个nextIndex,表示leader给各个follower发送的下一条log entry在log中的index,初始化为leader的最后一条log entry的下一个位置。leader 发送确认消息,如果发现 follower 当前的 log entry 与自己的不一致,则会进行比较,然后发送少了的日志。由于在选举时已经确保了 leader 会比大多数节点拥有更新的日志,所以几乎不用考虑follower 的日志比 leader 更新的情况
安全性问题
raft 协议中只允许主节点提交当前 term 的日志,不能提交之前 term 的日志。这会可能会导致日志被截断,这是一致性协议不能容忍的。
日志压缩
在实际的系统中,不能让日志无限增长,否则系统重启时需要花很长的时间进行回放,从而影响availability。Raft采用对整个系统进行snapshot来处理,snapshot之前的日志都可以丢弃。Snapshot技术在Chubby和ZooKeeper系统中都有采用。
Raft使用的方案是:每个副本独立的对自己的系统状态进行Snapshot,并且只能对已经提交的日志记录(已经应用到状态机)进行snapshot。
snapshot 中包含日志数据和状态机数据两部分
集群拓扑变化
集群拓扑变化的意思是在运行过程中多副本集群的结构性变化,如增加/减少副本数、节点替换等。
Raft协议定义时也考虑了这种情况,从而避免由于下线老集群上线新集群而引起的系统不可用。Raft也是利用上面的Log Entry和一致性协议来实现该功能。
假设在Raft中,老集群配置用Cold表示,新集群配置用Cnew表示,整个集群拓扑变化的流程如下:
1.当集群成员配置改变时,leader收到人工发出的重配置命令从Cold切成Cnew;
2.Leader副本在本地生成一个新的log entry,其内容是Cold∪Cnew,代表当前时刻新旧拓扑配置共存,写入本地日志,同时将该log entry推送至其他Follower节点
3.Follower副本收到log entry后更新本地日志,并且此时就以该配置作为自己了解的全局拓扑结构,
4.如果多数Follower确认了Cold U Cnew这条日志的时候,Leader就Commit这条log entry;
5.接下来Leader生成一条新的log entry,其内容是全新的配置Cnew,同样将该log entry写入本地日志,同时推送到Follower上;
6.Follower收到新的配置日志Cnew后,将其写入日志,并且从此刻起,就以该新的配置作为系统拓扑,并且如果发现自己不在Cnew这个配置中会自动退出
7.Leader收到多数Follower的确认消息以后,给客户端发起命令执行成功的消息