成员变更是在集群运行过程中副本发生变化,如增加/减少副本数,节点替换等。
成员变更也是一个分布式一致性的问题,既所有服务器对成员新成员达成一致。但是成员变更又有其特殊性,因为成员变更的一致性达成的过程中,参与投票的过程会发生变化。
如果将成员变更当成一般的一致性问题,直接向Leader发送成员变更请求,Leader复制成员变更日志,达成多数之后提交,各个服务器提交成员变更日志后从日志成员(Cold)切换到最新成员配置(Cnew的时刻不同.
成员变更不能影响服务的可用性,但是成员变更过程的某一时刻,可能出现Cold和Cnew中同时存在两个不相交的多数派,进而可能选出两个Leader,形成不同的决议,破坏安全性。
由于成员变更的这一特殊性,成员变更不能当成一般的一致性问题去解决。
为了解决这一问题.Raft提出了两段的成员变更方法。集群先成旧成员配置Cold切换到一个过度的配置,称为共同一致(joint consensus),共同一致时旧成员配置Cold和新成员配置Cnew的组合Cold U Cnew,一旦共同一致Cold U Cnew被提交,系统在切换到新成员配置Cnew。
一个配置切换的时间线。虚线表示已经被创建但是还没有被提交的条目,实线表示最后被提交的日志条目。领导人首先创建了C-old
,new的配置条目在自己的日志中,并提交到C-old,new中(C-old的大多数和c-new的大多数)。然后他创建C-new条目并且提交到C-new的大多数。这样就不存在C-new和C-old同时做出决定的时间点。
在关于重新配置还有三个问题需要提出,第一个问题是,新的服务器额能初始化没有存储任何的日志条目。当这些服务器以这种状态加入到集群中,那么它们需要一段时间来更新追赶。这时还不能提交新的日志条目。为了避免这种可用性的间隔时间Raft在配置更新的时候用了一种额外的阶段,在这种阶段,新的服务器以没有投票权的身份加入集群中来(领导人复制日志给它们。但是不考虑它们是大多数)。一旦新的服务器追赶上了集群中的集群,重新配置可以向上面描述一样处理。
第二个问题,集群的领导人可能不是新配置的一员。在这种情况下,领导人就会在提交了C-new日志后退位(回到追随者状态)。这意味着有这样一段时间,领导人管理着集群,但是不包括他自己,他复制日志但是不把他自己算作大多数之一。当C-new被提交时,会发生领导人过度。因为这时时最新的配置可以独立工作时间点(将总是能够在C-new配置下选出新的Leader)。再此之前,可能只从C-old中选出领导人。
第三个问题是:移除不再C-new中的服务器可能会扰乱集群。这些服务器将不会再接收心跳。当选举超时时,它们就会进行新的选举过程。它们会发送拥有新的任期号的请求投票RPCs,这样会导致当前的领导人退回成跟随者状态。新的领导人最终被选出来,但是被移除的服务器将会再次超时,然后这种过程再次重复,导致整体可用性大幅度下降。
为了避免这个问题,当服务器确认当前领导人存在时,服务器会忽略投票RPCs。特别的,当服务器再当前最小选举超时时间内收到一个请求投票的RPC。他不会更新当前的任期号和投票号。这不会影响正常的选举,每个服务器在开始一次选举之前,至少等待一个最小选举超时时间。然后这有利于避免被移除的服务器的扰乱。如果领导人能够发送心跳给集群,那么他就不会更大的任期号废黜。
日志压缩在实际系统中,不能让日志无限增长,否则系统重启时需要花很长的时间回放,从而影响可用性。Raft采用对整个系统进行snapshot来解决,snapshot之前的日志都可以抛弃。
每个副本独立的对自己系统状态进行snapshot,并且只能对已经提交的日志进行snapshot。
Snapshot中包含以下内容:
1>日志元数据:最后提交的log entry的log index和term。这两个值在snapshot之后的第一条log entry的AppendEntriesRPC的完整性检查的时候会被用上。
2> 系统当前状态。
当Leader要发给某个日志落后太多的Follower的log entry被丢弃,Leader会将snapshot发给Follower。或者新加入一台机器时,也会发送snapshot给它。发送snapshot使用InstalledSnapshot RPC。
一个服务器用新的快照替换了从1到5的条目数,快照存储了当前的状态。快照中包含了最后的索引位置和任期号